1 #include "Pathfinder.h"
2
3 #include "../util/Logger.h"
4 #include "../util/ScopedTimer.h"
5 #include "../util/AppInterface.h"
6 #include "../Empire/EmpireManager.h"
7 #include "Field.h"
8 #include "Fleet.h"
9 #include "Ship.h"
10 #include "System.h"
11 #include "UniverseObject.h"
12 #include "Universe.h"
13 #include "Predicates.h"
14
15 #include <boost/graph/adjacency_list.hpp>
16 #include <boost/graph/breadth_first_search.hpp>
17 #include <boost/graph/dijkstra_shortest_paths.hpp>
18 #include <boost/graph/filtered_graph.hpp>
19 #include <boost/thread/locks.hpp>
20 #include <boost/thread/shared_mutex.hpp>
21 #include <boost/variant/variant.hpp>
22
23 #include <algorithm>
24 #include <stdexcept>
25 #include <limits>
26
27 namespace {
28 const double WORMHOLE_TRAVEL_DISTANCE = 0.1; // the effective distance for ships travelling along a wormhole, for determining how much of their speed is consumed by the jump
29 }
30
31 FO_COMMON_API extern const int ALL_EMPIRES;
32
33 namespace {
34
35 /** distance_matrix_storage implements the storage and the mutexes
36 for distance in number of hops from system to system.
37
38 For N systems there are N rows of N integer types T.
39 Each row has a mutex and the whole table has a mutex.
40
41 The table is assumed symmetric. If present row i element j will
42 equal row j element i.
43 */
44 template <typename T>
45 struct distance_matrix_storage {
46 typedef T value_type; ///< An integral type for number of hops.
47 typedef std::vector<T>& row_ref; ///< A reference to row type
48
distance_matrix_storage__anonfbdc549a0211::distance_matrix_storage49 distance_matrix_storage() {};
distance_matrix_storage__anonfbdc549a0211::distance_matrix_storage50 distance_matrix_storage(const distance_matrix_storage<T>& src)
51 { resize(src.m_data.size()); };
52 //TODO C++11 Move Constructor.
53
54 /**Number of rows and columns. (N)*/
size__anonfbdc549a0211::distance_matrix_storage55 size_t size()
56 { return m_data.size(); }
57
58 /**Resize and clear all mutexes. Assumes that table is locked.*/
resize__anonfbdc549a0211::distance_matrix_storage59 void resize(size_t a_size) {
60 m_data.clear();
61 m_data.resize(a_size);
62 m_row_mutexes.resize(a_size);
63 for (auto &row_mutex : m_row_mutexes)
64 row_mutex = std::make_shared<boost::shared_mutex>();
65 }
66
67 /**N x N table of hop distances in row column form.*/
68 std::vector< std::vector<T>> m_data;
69 /**Per row mutexes.*/
70 std::vector< std::shared_ptr<boost::shared_mutex>> m_row_mutexes;
71 /**Table mutex*/
72 boost::shared_mutex m_mutex;
73 };
74
75
76 /**distance_matrix_cache is a cache of symmetric hop distances
77 based on distance_matrix_storage.
78
79 It enforces the locking convention with get_T which returns either a single
80 integral value or calls a cache miss handler to fill an entire write locked
81 row with data and then returns the requested value.
82
83 All operations first lock the table and then when necessary the row.
84 */
85 //TODO: Consider replacing this scheme with thread local storage. It
86 //may be true that because this is a computed value that depends on
87 //the system topology that almost never changes that the
88 //synchronization costs way out-weigh the saved computation costs.
89 template <typename Storage, typename T = typename Storage::value_type, typename Row = typename Storage::row_ref>
90 class distance_matrix_cache {
91 public:
distance_matrix_cache(Storage & the_storage)92 distance_matrix_cache(Storage& the_storage) : m_storage(the_storage) {}
93 /**Read lock the table and return the size N.*/
size()94 size_t size() {
95 boost::shared_lock<boost::shared_mutex> guard(m_storage.m_mutex);
96 return m_storage.size();
97 }
98 /**Write lock the table and resize to N = \p a_size.*/
resize(size_t a_size)99 void resize(size_t a_size) {
100 boost::unique_lock<boost::shared_mutex> guard(m_storage.m_mutex);
101 m_storage.resize(a_size);
102 }
103
104 public:
105
106 // Note: The signatures for the cache miss handler and the cache hit handler have a void
107 // return type, because this code can not anticipate all expected return types. In order
108 // to return a result function the calling code will need to bind the return parameter to
109 // the function so that the function still has the required signature.
110
111 /// Cache miss handler
112 typedef std::function<void (size_t &/*ii*/, Row /*row*/)> cache_miss_handler;
113
114 /// A function to examine an entire row cache hit
115 typedef std::function<void (size_t &/*ii*/, const Row /*row*/)> cache_hit_handler;
116
117 /** Retrieve a single element at (\p ii, \p jj).
118 * On cache miss call the \p fill_row which must fill the row
119 * at \p ii with NN items.
120 * Throws if either index is out of range or if \p fill_row
121 * does not fill the row on a cache miss.
122 */
get_T(size_t ii,size_t jj,cache_miss_handler fill_row) const123 T get_T(size_t ii, size_t jj, cache_miss_handler fill_row) const {
124 boost::shared_lock<boost::shared_mutex> guard(m_storage.m_mutex);
125
126 size_t NN = m_storage.size();
127 if ((ii >= NN) || (jj >= NN)) {
128 ErrorLogger() << "distance_matrix_cache::get_T passed invalid node indices: "
129 << ii << "," << jj << " matrix size: " << NN;
130 throw std::out_of_range("row and/or column index is invalid.");
131 }
132 {
133 boost::shared_lock<boost::shared_mutex> row_guard(*m_storage.m_row_mutexes[ii]);
134 const Row &row_data = m_storage.m_data[ii];
135
136 if (NN == row_data.size())
137 return row_data[jj];
138 }
139 {
140 boost::shared_lock<boost::shared_mutex> column_guard(*m_storage.m_row_mutexes[jj]);
141 const Row &column_data = m_storage.m_data[jj];
142
143 if (NN == column_data.size())
144 return column_data[ii];
145 }
146 {
147 boost::unique_lock<boost::shared_mutex> row_guard(*m_storage.m_row_mutexes[ii]);
148 Row &row_data = m_storage.m_data[ii];
149
150 if (NN == row_data.size()) {
151 return row_data[jj];
152 } else {
153 fill_row(ii, row_data);
154 if (row_data.size() != NN) {
155 std::stringstream ss;
156 ss << "Cache miss handler only filled cache row with "
157 << row_data.size() << " items when " << NN
158 << " items where expected ";
159 ErrorLogger() << ss.str();
160 throw std::out_of_range(ss.str());
161 }
162 return row_data[jj];
163 }
164 }
165 }
166
167 /** Retrieve a single row at \p ii.
168 * On cache miss call \p fill_row which must fill the row
169 * at \p ii with NN items.
170 * On cache hit call \p use_row to handle the cache hit
171 * Throws if index is out of range or if \p fill_row
172 * does not fill the row on a cache miss.
173 */
examine_row(size_t ii,cache_miss_handler fill_row,cache_hit_handler use_row) const174 void examine_row(size_t ii, cache_miss_handler fill_row, cache_hit_handler use_row) const {
175 boost::shared_lock<boost::shared_mutex> guard(m_storage.m_mutex);
176
177 size_t NN = m_storage.size();
178 if (ii >= NN) {
179 ErrorLogger() << "distance_matrix_cache::get_row passed invalid index: "
180 << ii << " matrix size: " << NN;
181 throw std::out_of_range("row index is invalid.");
182 }
183 {
184 boost::shared_lock<boost::shared_mutex> row_guard(*m_storage.m_row_mutexes[ii]);
185 const Row &row_data = m_storage.m_data[ii];
186
187 if (NN == row_data.size())
188 return use_row(ii, row_data);
189 }
190 {
191 boost::unique_lock<boost::shared_mutex> row_guard(*m_storage.m_row_mutexes[ii]);
192 Row &row_data = m_storage.m_data[ii];
193
194 if (NN == row_data.size()) {
195 return use_row(ii, row_data);
196 } else {
197 fill_row(ii, row_data);
198 if (row_data.size() != NN) {
199 std::stringstream ss;
200 ss << "Cache miss handler only filled cache row with "
201 << row_data.size() << " items when " << NN
202 << " items where expected ";
203 ErrorLogger() << ss.str();
204 throw std::range_error(ss.str());
205 }
206 return use_row(ii, row_data);
207 }
208 }
209 }
210
211 private:
212 Storage& m_storage;
213 };
214 }
215
216
217 namespace SystemPathing {
218 /** Used to short-circuit the use of BFS (breadth-first search) or
219 * Dijkstra's algorithm for pathfinding when it finds the desired
220 * destination system. */
221 struct PathFindingShortCircuitingVisitor : public boost::base_visitor<PathFindingShortCircuitingVisitor>
222 {
223 typedef boost::on_finish_vertex event_filter;
224
225 struct FoundDestination {}; // exception type thrown when destination is found
226
PathFindingShortCircuitingVisitorSystemPathing::PathFindingShortCircuitingVisitor227 PathFindingShortCircuitingVisitor(int dest_system) : destination_system(dest_system) {}
228 template <typename Vertex, typename Graph>
operator ()SystemPathing::PathFindingShortCircuitingVisitor229 void operator()(Vertex u, Graph& g)
230 {
231 if (static_cast<int>(u) == destination_system)
232 throw FoundDestination();
233 }
234 const int destination_system;
235 };
236
237 /** Complete BFS visitor implementing:
238 * - predecessor recording
239 * - short-circuit exit on found match
240 * - maximum search depth
241 */
242 template <typename Graph, typename Edge, typename Vertex>
243 class BFSVisitorImpl
244 {
245 public:
246 class FoundDestination {};
247 class ReachedDepthLimit {};
248
249 private:
250 Vertex m_marker;
251 Vertex m_stop;
252 Vertex m_source;
253 Vertex * m_predecessors;
254 int m_levels_remaining;
255 bool m_level_complete;
256
257 public:
BFSVisitorImpl(const Vertex & start,const Vertex & stop,Vertex predecessors[],int max_depth)258 BFSVisitorImpl(const Vertex& start, const Vertex& stop, Vertex predecessors[], int max_depth)
259 : m_marker(start),
260 m_stop(stop),
261 m_source(start),
262 m_predecessors(predecessors),
263 m_levels_remaining(max_depth),
264 m_level_complete(false)
265 {}
266
initialize_vertex(const Vertex & v,const Graph & g)267 void initialize_vertex(const Vertex& v, const Graph& g) {}
268
discover_vertex(const Vertex & v,const Graph & g)269 void discover_vertex(const Vertex& v, const Graph& g) {
270 m_predecessors[static_cast<int>(v)] = m_source;
271
272 if (v == m_stop)
273 throw FoundDestination();
274
275 if (m_level_complete) {
276 m_marker = v;
277 m_level_complete = false;
278 }
279 }
280
examine_vertex(const Vertex & v,const Graph & g)281 void examine_vertex(const Vertex& v, const Graph& g) {
282 if (v == m_marker) {
283 if (!m_levels_remaining)
284 throw ReachedDepthLimit();
285 m_levels_remaining--;
286 m_level_complete = true;
287 }
288
289 m_source = v; // avoid re-calculating source from edge
290 }
291
examine_edge(const Edge & e,const Graph & g)292 void examine_edge(const Edge& e, const Graph& g) {}
tree_edge(const Edge & e,const Graph & g)293 void tree_edge(const Edge& e, const Graph& g) {} // wait till target is calculated
294
295
non_tree_edge(const Edge & e,const Graph & g)296 void non_tree_edge(const Edge& e, const Graph& g) {}
gray_target(const Edge & e,const Graph & g)297 void gray_target(const Edge& e, const Graph& g) {}
black_target(const Edge & e,const Graph & g)298 void black_target(const Edge& e, const Graph& g) {}
finish_vertex(const Vertex & e,const Graph & g)299 void finish_vertex(const Vertex& e, const Graph& g) {}
300 };
301
302 ////////////////////////////////////////////////////////////////
303 // templated implementations of Universe graph search methods //
304 ////////////////////////////////////////////////////////////////
305 struct vertex_system_id_t {typedef boost::vertex_property_tag kind;}; ///< a system graph property map type
306
307 /** Returns the path between vertices \a system1_id and \a system2_id of
308 * \a graph that travels the shorest distance on starlanes, and the path
309 * length. If system1_id is the same vertex as system2_id, the path has
310 * just that system in it, and the path lenth is 0. If there is no path
311 * between the two vertices, then the list is empty and the path length
312 * is -1.0 */
313 template <typename Graph>
ShortestPathImpl(const Graph & graph,int system1_id,int system2_id,double linear_distance,const boost::unordered_map<int,size_t> & id_to_graph_index)314 std::pair<std::list<int>, double> ShortestPathImpl(const Graph& graph, int system1_id, int system2_id,
315 double linear_distance, const boost::unordered_map<int, size_t>& id_to_graph_index)
316 {
317 typedef typename boost::property_map<Graph, vertex_system_id_t>::const_type ConstSystemIDPropertyMap;
318 typedef typename boost::property_map<Graph, boost::vertex_index_t>::const_type ConstIndexPropertyMap;
319 typedef typename boost::property_map<Graph, boost::edge_weight_t>::const_type ConstEdgeWeightPropertyMap;
320
321 std::pair<std::list<int>, double> retval(std::list<int>(), -1.0);
322
323 ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), graph);
324
325 // convert system IDs to graph indices. try/catch for invalid input system ids.
326 size_t system1_index, system2_index;
327 try {
328 system1_index = id_to_graph_index.at(system1_id);
329 system2_index = id_to_graph_index.at(system2_id);
330 } catch (...) {
331 return retval;
332 }
333
334 // early exit if systems are the same
335 if (system1_id == system2_id) {
336 retval.first.push_back(system2_id);
337 retval.second = 0.0; // no jumps needed -> 0 distance
338 return retval;
339 }
340
341 /* initializing all vertices' predecessors to themselves prevents endless loops when back traversing the tree in the case where
342 one of the end systems is system 0, because systems that are not connected to the root system (system2) are not visited
343 by the search, and so their predecessors are left unchanged. Default initialization of the vector may be 0 or undefined
344 which could lead to out of bounds errors, or endless loops if a system's default predecessor is 0 (debug mode), and 0's
345 predecessor is that system */
346 std::vector<int> predecessors(boost::num_vertices(graph));
347 std::vector<double> distances(boost::num_vertices(graph));
348 for (unsigned int i = 0; i < boost::num_vertices(graph); ++i) {
349 predecessors[i] = i;
350 distances[i] = -1.0;
351 }
352
353
354 ConstIndexPropertyMap index_map = boost::get(boost::vertex_index, graph);
355 ConstEdgeWeightPropertyMap edge_weight_map = boost::get(boost::edge_weight, graph);
356
357
358 // do the actual path finding using verbose boost magic...
359 try {
360 boost::dijkstra_shortest_paths(graph, system1_index, &predecessors[0], &distances[0], edge_weight_map, index_map,
361 std::less<double>(), std::plus<double>(), std::numeric_limits<int>::max(), 0,
362 boost::make_dijkstra_visitor(PathFindingShortCircuitingVisitor(system2_index)));
363 } catch (const PathFindingShortCircuitingVisitor::FoundDestination&) {
364 // catching this just means that the destination was found, and so the algorithm was exited early, via exception
365 }
366
367
368 int current_system = system2_index;
369 while (predecessors[current_system] != current_system) {
370 retval.first.push_front(sys_id_property_map[current_system]);
371 current_system = predecessors[current_system];
372 }
373 retval.second = distances[system2_index];
374
375 if (retval.first.empty()) {
376 // there is no path between the specified nodes
377 retval.second = -1.0;
378 return retval;
379 } else {
380 // add start system to path, as it wasn't added by traversing predecessors array
381 retval.first.push_front(sys_id_property_map[system1_index]);
382 }
383
384 return retval;
385 }
386
387 /** Returns the path between vertices \a system1_id and \a system2_id of
388 * \a graph that takes the fewest number of jumps (edge traversals), and
389 * the number of jumps this path takes. If system1_id is the same vertex
390 * as system2_id, the path has just that system in it, and the path lenth
391 * is 0. If there is no path between the two vertices, then the list is
392 * empty and the path length is -1 */
393 template <typename Graph>
LeastJumpsPathImpl(const Graph & graph,int system1_id,int system2_id,const boost::unordered_map<int,size_t> & id_to_graph_index,int max_jumps=INT_MAX)394 std::pair<std::list<int>, int> LeastJumpsPathImpl(const Graph& graph, int system1_id, int system2_id,
395 const boost::unordered_map<int, size_t>& id_to_graph_index,
396 int max_jumps = INT_MAX)
397 {
398 typedef typename boost::property_map<Graph, vertex_system_id_t>::const_type ConstSystemIDPropertyMap;
399
400 ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), graph);
401 std::pair<std::list<int>, int> retval;
402
403 size_t system1_index = id_to_graph_index.at(system1_id);
404 size_t system2_index = id_to_graph_index.at(system2_id);
405
406 // early exit if systems are the same
407 if (system1_id == system2_id) {
408 retval.first.push_back(system2_id);
409 retval.second = 0; // no jumps needed
410 return retval;
411 }
412
413 /* initializing all vertices' predecessors to themselves prevents endless loops when back traversing the tree in the case where
414 one of the end systems is system 0, because systems that are not connected to the root system (system2) are not visited
415 by the search, and so their predecessors are left unchanged. Default initialization of the vector may be 0 or undefined
416 which could lead to out of bounds errors, or endless loops if a system's default predecessor is 0, (debug mode) and 0's
417 predecessor is that system */
418 std::vector<int> predecessors(boost::num_vertices(graph));
419 for (unsigned int i = 0; i < boost::num_vertices(graph); ++i)
420 predecessors[i] = i;
421
422
423 // do the actual path finding using verbose boost magic...
424 typedef BFSVisitorImpl<Graph, typename boost::graph_traits<Graph>::edge_descriptor, int> BFSVisitor;
425 try {
426 boost::queue<int> buf;
427 std::vector<int> colors(boost::num_vertices(graph));
428
429 BFSVisitor bfsVisitor(system1_index, system2_index, &predecessors[0], max_jumps);
430 boost::breadth_first_search(graph, system1_index, buf, bfsVisitor, &colors[0]);
431 } catch (const typename BFSVisitor::ReachedDepthLimit&) {
432 // catching this means the algorithm explored the neighborhood until max_jumps and didn't find anything
433 return std::make_pair(std::list<int>(), -1);
434 } catch (const typename BFSVisitor::FoundDestination&) {
435 // catching this just means that the destination was found, and so the algorithm was exited early, via exception
436 }
437
438
439 int current_system = system2_index;
440 while (predecessors[current_system] != current_system) {
441 retval.first.push_front(sys_id_property_map[current_system]);
442 current_system = predecessors[current_system];
443 }
444 retval.second = retval.first.size() - 1; // number of jumps is number of systems in path minus one for the starting system
445
446 if (retval.first.empty()) {
447 // there is no path between the specified nodes
448 retval.second = -1;
449 } else {
450 // add start system to path, as it wasn't added by traversing predecessors array
451 retval.first.push_front(sys_id_property_map[system1_index]);
452 }
453
454 return retval;
455 }
456
457 template <typename Graph>
ImmediateNeighborsImpl(const Graph & graph,int system_id,const boost::unordered_map<int,size_t> & id_to_graph_index)458 std::multimap<double, int> ImmediateNeighborsImpl(const Graph& graph, int system_id,
459 const boost::unordered_map<int, size_t>& id_to_graph_index)
460 {
461 typedef typename Graph::out_edge_iterator OutEdgeIterator;
462 typedef typename boost::property_map<Graph, vertex_system_id_t>::const_type ConstSystemIDPropertyMap;
463 typedef typename boost::property_map<Graph, boost::edge_weight_t>::const_type ConstEdgeWeightPropertyMap;
464
465 std::multimap<double, int> retval;
466 ConstEdgeWeightPropertyMap edge_weight_map = boost::get(boost::edge_weight, graph);
467 ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), graph);
468 auto edges = boost::out_edges(id_to_graph_index.at(system_id), graph);
469 for (OutEdgeIterator it = edges.first; it != edges.second; ++it) {
470 retval.insert({edge_weight_map[*it],
471 sys_id_property_map[boost::target(*it, graph)]});
472 }
473 return retval;
474 }
475
476
477 }
478 using namespace SystemPathing; // to keep GCC 4.2 on OSX happy
479
480 namespace {
481 /////////////////////////////////////////////
482 // struct GraphImpl
483 /////////////////////////////////////////////
484 struct GraphImpl {
485 typedef boost::property<vertex_system_id_t, int,
486 boost::property<boost::vertex_index_t, int>> vertex_property_t; ///< a system graph property map type
487 typedef boost::property<boost::edge_weight_t, double> edge_property_t; ///< a system graph property map type
488
489 // declare main graph types, including properties declared above
490 // could add boost::disallow_parallel_edge_tag GraphProperty but it doesn't
491 // work for vecS vector-based lists and parallel edges can be avoided while
492 // creating the graph by filtering the edges to be added
493 typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
494 vertex_property_t, edge_property_t> SystemGraph;
495
496 struct EdgeVisibilityFilter {
EdgeVisibilityFilter__anonfbdc549a0311::GraphImpl::EdgeVisibilityFilter497 EdgeVisibilityFilter() {}
498
EdgeVisibilityFilter__anonfbdc549a0311::GraphImpl::EdgeVisibilityFilter499 EdgeVisibilityFilter(const SystemGraph* graph, int empire_id) :
500 m_graph(graph),
501 m_empire_id(empire_id)
502 {
503 if (!graph)
504 ErrorLogger() << "EdgeVisibilityFilter passed null graph pointer";
505 }
506
507 template <typename EdgeDescriptor>
operator ()__anonfbdc549a0311::GraphImpl::EdgeVisibilityFilter508 bool operator()(const EdgeDescriptor& edge) const
509 {
510 if (!m_graph)
511 return false;
512
513 // get system ids from graph indices
514 ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), *m_graph); // for reverse-lookup System universe ID from graph index
515 int sys_graph_index_1 = boost::source(edge, *m_graph);
516 int sys_id_1 = sys_id_property_map[sys_graph_index_1];
517 int sys_graph_index_2 = boost::target(edge, *m_graph);
518 int sys_id_2 = sys_id_property_map[sys_graph_index_2];
519
520 // look up lane between systems
521 std::shared_ptr<const System> system1 = EmpireKnownObjects(m_empire_id).get<System>(sys_id_1);
522 if (!system1) {
523 ErrorLogger() << "EdgeDescriptor::operator() couldn't find system with id " << sys_id_1;
524 return false;
525 }
526 if (system1->HasStarlaneTo(sys_id_2))
527 return true;
528
529 // lane not found
530 return false;
531 }
532
533 private:
534 const SystemGraph* m_graph = nullptr;
535 int m_empire_id = ALL_EMPIRES;
536 };
537 typedef boost::filtered_graph<SystemGraph, EdgeVisibilityFilter> EmpireViewSystemGraph;
538 typedef std::map<int, std::shared_ptr<EmpireViewSystemGraph>> EmpireViewSystemGraphMap;
539
540
AddSystemPredicate__anonfbdc549a0311::GraphImpl541 void AddSystemPredicate(const Pathfinder::SystemExclusionPredicateType& pred) {
542 for (auto empire : Empires()) {
543 auto empire_id = empire.first;
544 SystemPredicateFilter sys_pred_filter(&system_graph, empire_id, pred);
545 auto sys_pred_filtered_graph_ptr = std::make_shared<SystemPredicateGraph>(system_graph, sys_pred_filter);
546
547 auto pred_it = system_pred_graph_views.find(pred);
548 if (pred_it == system_pred_graph_views.end()) {
549 EmpireSystemPredicateMap empire_graph_map;
550 empire_graph_map.emplace(empire_id, std::move(sys_pred_filtered_graph_ptr));
551 system_pred_graph_views.emplace(pred, std::move(empire_graph_map));
552 } else if (pred_it->second.count(empire_id)) {
553 pred_it->second.at(empire_id) = std::move(sys_pred_filtered_graph_ptr);
554 } else {
555 pred_it->second.emplace(empire_id, std::move(sys_pred_filtered_graph_ptr));
556 }
557 }
558 }
559
560 struct SystemPredicateFilter {
SystemPredicateFilter__anonfbdc549a0311::GraphImpl::SystemPredicateFilter561 SystemPredicateFilter() {}
562
SystemPredicateFilter__anonfbdc549a0311::GraphImpl::SystemPredicateFilter563 SystemPredicateFilter(const SystemGraph* graph, int empire_id,
564 const Pathfinder::SystemExclusionPredicateType& pred) :
565 m_graph(graph),
566 m_empire_id(empire_id),
567 m_pred(pred)
568 {
569 if (!graph)
570 ErrorLogger() << "ExcludeObjectFilter passed null graph pointer";
571 }
572
573 template <typename EdgeDescriptor>
operator ()__anonfbdc549a0311::GraphImpl::SystemPredicateFilter574 bool operator()(const EdgeDescriptor& edge) const {
575 if (!m_graph)
576 return true;
577
578 // get system ids from graph indices
579
580 // for reverse-lookup System universe ID from graph index
581 ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), *m_graph);
582 int sys_graph_index_1 = boost::source(edge, *m_graph);
583 int sys_id_1 = sys_id_property_map[sys_graph_index_1];
584 int sys_graph_index_2 = boost::target(edge, *m_graph);
585 int sys_id_2 = sys_id_property_map[sys_graph_index_2];
586
587 // look up objects in system
588 auto system1 = EmpireKnownObjects(m_empire_id).get<System>(sys_id_1);
589 if (!system1) {
590 ErrorLogger() << "Invalid source system " << sys_id_1;
591 return true;
592 }
593 auto system2 = EmpireKnownObjects(m_empire_id).get<System>(sys_id_2);
594 if (!system2) {
595 ErrorLogger() << "Invalid target system " << sys_id_2;
596 return true;
597 }
598
599 if (!system1->HasStarlaneTo(system2->ID())) {
600 DebugLogger() << "No starlane from " << system1->ID() << " to " << system2->ID();
601 return false;
602 }
603
604 // Discard edge if it finds a contained object or matches either system for visitor
605 for (auto object : EmpireKnownObjects(m_empire_id).find(*m_pred.get())) {
606 if (!object)
607 continue;
608 // object is destination system
609 if (object->ID() == system2->ID())
610 return false;
611
612 // object contained by destination system
613 if (object->ContainedBy(system2->ID()))
614 return false;
615 }
616
617 return true;
618 }
619
620 private:
621 const SystemGraph* m_graph = nullptr;
622 int m_empire_id = ALL_EMPIRES;
623 Pathfinder::SystemExclusionPredicateType m_pred;
624 };
625 typedef boost::filtered_graph<SystemGraph, SystemPredicateFilter> SystemPredicateGraph;
626 typedef std::map<int, std::shared_ptr<SystemPredicateGraph>> EmpireSystemPredicateMap;
627 typedef std::map<Pathfinder::SystemExclusionPredicateType, EmpireSystemPredicateMap> SystemPredicateGraphMap;
628
629 // declare property map types for properties declared above
630 typedef boost::property_map<SystemGraph, vertex_system_id_t>::const_type ConstSystemIDPropertyMap;
631 typedef boost::property_map<SystemGraph, vertex_system_id_t>::type SystemIDPropertyMap;
632 typedef boost::property_map<SystemGraph, boost::vertex_index_t>::const_type ConstIndexPropertyMap;
633 typedef boost::property_map<SystemGraph, boost::vertex_index_t>::type IndexPropertyMap;
634 typedef boost::property_map<SystemGraph, boost::edge_weight_t>::const_type ConstEdgeWeightPropertyMap;
635 typedef boost::property_map<SystemGraph, boost::edge_weight_t>::type EdgeWeightPropertyMap;
636
637 SystemGraph system_graph; ///< a graph in which the systems are vertices and the starlanes are edges
638 EmpireViewSystemGraphMap empire_system_graph_views; ///< a map of empire IDs to the views of the system graph by those empires
639 /** Empire system graphs indexed by object predicate */
640 SystemPredicateGraphMap system_pred_graph_views;
641 std::unordered_set<Pathfinder::SystemExclusionPredicateType> system_predicates;
642 };
643
644 }
645
646 /////////////////////////////////////////////
647 // class PathfinderImpl
648 /////////////////////////////////////////////
649 class Pathfinder::PathfinderImpl {
650 public:
651
PathfinderImpl()652 PathfinderImpl(): m_graph_impl(new GraphImpl) {}
653
654 double LinearDistance(int object1_id, int object2_id) const;
655 short JumpDistanceBetweenSystems(int system1_id, int system2_id) const;
656 int JumpDistanceBetweenObjects(int object1_id, int object2_id) const;
657
658 std::pair<std::list<int>, double> ShortestPath(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const;
659 std::pair<std::list<int>, double> ShortestPath(int system1_id, int system2_id, int empire_id,
660 const Pathfinder::SystemExclusionPredicateType& sys_pred) const;
661 double ShortestPathDistance(int object1_id, int object2_id) const;
662 std::pair<std::list<int>, int> LeastJumpsPath(
663 int system1_id, int system2_id, int empire_id = ALL_EMPIRES, int max_jumps = INT_MAX) const;
664 bool SystemsConnected(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const;
665 bool SystemHasVisibleStarlanes(int system_id, int empire_id = ALL_EMPIRES) const;
666 std::multimap<double, int> ImmediateNeighbors(int system_id, int empire_id = ALL_EMPIRES) const;
667
668 boost::unordered_multimap<int, int> Neighbors(
669 int system_id, size_t n_outer = 1, size_t n_inner = 0) const;
670
671 /** When a cache hit occurs use \p row to populate and return the
672 multimap for Neighbors.*/
673 void NeighborsCacheHit(
674 boost::unordered_multimap<int, int>& result, size_t _n_outer, size_t _n_inner,
675 size_t ii, distance_matrix_storage<short>::row_ref row) const;
676
677 std::unordered_set<int> WithinJumps(size_t jumps, const std::vector<int>& candidates) const;
678 void WithinJumpsCacheHit(
679 std::unordered_set<int>* result, size_t jump_limit,
680 size_t ii, distance_matrix_storage<short>::row_ref row) const;
681
682 std::pair<std::vector<std::shared_ptr<const UniverseObject>>, std::vector<std::shared_ptr<const UniverseObject>>>
683 WithinJumpsOfOthers(
684 int jumps,
685 const std::vector<std::shared_ptr<const UniverseObject>>& candidates,
686 const std::vector<std::shared_ptr<const UniverseObject>>& stationary) const;
687
688 /**Return true if \p system_id is within \p jumps of any of \p others*/
689 bool WithinJumpsOfOthers(
690 int jumps, int system_id,
691 const std::vector<std::shared_ptr<const UniverseObject>>& others) const;
692
693 /** If any of \p others are within \p jumps of \p ii return true in \p answer.
694
695 The return value must be in a parameter so that after being bound to \p answer and \p jumps
696 the function signature is that required by the cache_hit_handler type.
697 */
698 void WithinJumpsOfOthersCacheHit(
699 bool& answer, int jumps,
700 const std::vector<std::shared_ptr<const UniverseObject>>& others,
701 size_t ii, distance_matrix_storage<short>::row_ref row) const;
702
703 int NearestSystemTo(double x, double y) const;
704 void InitializeSystemGraph(const std::vector<int> system_ids, int for_empire_id = ALL_EMPIRES);
705 void UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id = ALL_EMPIRES);
706
707 /** When a cache miss occurs fill \p row with the distances
708 from index \p ii to every other index.*/
709 void HandleCacheMiss(size_t ii, distance_matrix_storage<short>::row_ref row) const;
710
711
712 mutable distance_matrix_storage<short> m_system_jumps; ///< indexed by system graph index (not system id), caches the smallest number of jumps to travel between all the systems
713 std::shared_ptr<GraphImpl> m_graph_impl; ///< a graph in which the systems are vertices and the starlanes are edges
714 boost::unordered_map<int, size_t> m_system_id_to_graph_index;
715 };
716
717 /////////////////////////////////////////////
718 // class Pathfinder
719 /////////////////////////////////////////////
Pathfinder()720 Pathfinder::Pathfinder() :
721 pimpl(new PathfinderImpl)
722 {}
723
~Pathfinder()724 Pathfinder::~Pathfinder()
725 {}
726
727 namespace {
FleetFromObject(const std::shared_ptr<const UniverseObject> & obj)728 std::shared_ptr<const Fleet> FleetFromObject(const std::shared_ptr<const UniverseObject>& obj) {
729 std::shared_ptr<const Fleet> retval = std::dynamic_pointer_cast<const Fleet>(obj);
730 if (!retval) {
731 if (auto ship = std::dynamic_pointer_cast<const Ship>(obj))
732 retval = Objects().get<Fleet>(ship->FleetID());
733 }
734 return retval;
735 }
736 }
737
738 /** HandleCacheMiss requires that \p row be locked by exterior context.
739 */
HandleCacheMiss(size_t ii,distance_matrix_storage<short>::row_ref row) const740 void Pathfinder::PathfinderImpl::HandleCacheMiss(size_t ii, distance_matrix_storage<short>::row_ref row) const {
741
742 typedef boost::iterator_property_map<std::vector<short>::iterator,
743 boost::identity_property_map> DistancePropertyMap;
744
745 distance_matrix_storage<short>::row_ref distance_buffer = row;
746 distance_buffer.assign(m_system_jumps.size(), SHRT_MAX);
747 distance_buffer[ii] = 0;
748 DistancePropertyMap distance_property_map(distance_buffer.begin());
749 boost::distance_recorder<DistancePropertyMap, boost::on_tree_edge> distance_recorder(distance_property_map);
750
751 // FIXME: we have computed the i row and the j column, but
752 // we are only utilizing the i row.
753
754 boost::breadth_first_search(m_graph_impl->system_graph, ii,
755 boost::visitor(boost::make_bfs_visitor(distance_recorder)));
756 }
757
758
759
LinearDistance(int system1_id,int system2_id) const760 double Pathfinder::LinearDistance(int system1_id, int system2_id) const {
761 return pimpl->LinearDistance(system1_id, system2_id);
762 }
763
LinearDistance(int system1_id,int system2_id) const764 double Pathfinder::PathfinderImpl::LinearDistance(int system1_id, int system2_id) const {
765 const auto system1 = Objects().get<System>(system1_id);
766 if (!system1) {
767 ErrorLogger() << "Universe::LinearDistance passed invalid system id: " << system1_id;
768 throw std::out_of_range("system1_id invalid");
769 }
770 const auto system2 = Objects().get<System>(system2_id);
771 if (!system2) {
772 ErrorLogger() << "Universe::LinearDistance passed invalid system id: " << system2_id;
773 throw std::out_of_range("system2_id invalid");
774 }
775 double x_dist = system2->X() - system1->X();
776 double y_dist = system2->Y() - system1->Y();
777 return std::sqrt(x_dist*x_dist + y_dist*y_dist);
778 }
779
JumpDistanceBetweenSystems(int system1_id,int system2_id) const780 short Pathfinder::JumpDistanceBetweenSystems(int system1_id, int system2_id) const {
781 return pimpl->JumpDistanceBetweenSystems(system1_id, system2_id);
782 }
783
JumpDistanceBetweenSystems(int system1_id,int system2_id) const784 short Pathfinder::PathfinderImpl::JumpDistanceBetweenSystems(int system1_id, int system2_id) const {
785 if (system1_id == system2_id)
786 return 0;
787
788 try {
789 distance_matrix_cache< distance_matrix_storage<short>> cache(m_system_jumps);
790
791 size_t system1_index = m_system_id_to_graph_index.at(system1_id);
792 size_t system2_index = m_system_id_to_graph_index.at(system2_id);
793 size_t smaller_index = (std::min)(system1_index, system2_index);
794 size_t other_index = (std::max)(system1_index, system2_index);
795
796 #if BOOST_VERSION >= 106000
797 using boost::placeholders::_1;
798 using boost::placeholders::_2;
799 #endif
800
801 // prefer filling the smaller row/column for increased cache locality
802 short jumps = cache.get_T(
803 smaller_index, other_index,
804 boost::bind(&Pathfinder::PathfinderImpl::HandleCacheMiss, this, _1, _2));
805 if (jumps == SHRT_MAX) // value returned for no valid path
806 return -1;
807 return jumps;
808 } catch (const std::out_of_range&) {
809 ErrorLogger() << "PathfinderImpl::JumpDistanceBetweenSystems passed invalid system id(s): "
810 << system1_id << " & " << system2_id;
811 throw;
812 }
813 }
814
815 namespace {
816 /**GeneralizedLocationType abstracts the location of a UniverseObject.
817
818 GeneralizedLocationType can be nowhere, one system or two systems in the
819 case of a ship or fleet in transit.
820 The returned variant is
821 null_ptr -- nowhere
822 System id -- somewhere
823 pair of System ids -- in transit
824
825 */
826
827 typedef boost::variant<std::nullptr_t, int, std::pair<int, int>> GeneralizedLocationType;
828
829 /** Return the location of \p obj.*/
GeneralizedLocation(const std::shared_ptr<const UniverseObject> & obj)830 GeneralizedLocationType GeneralizedLocation(const std::shared_ptr<const UniverseObject>& obj) {
831 if (!obj)
832 return nullptr;
833
834 int system_id = obj->SystemID();
835 auto system = Objects().get<System>(system_id);
836 if (system)
837 return system_id;
838
839 auto fleet = FleetFromObject(obj);
840 if (fleet)
841 return std::make_pair(fleet->PreviousSystemID(), fleet->NextSystemID());
842
843 if (std::dynamic_pointer_cast<const Field>(obj))
844 return nullptr;
845
846 // Don't generate an error message for temporary objects.
847 if (obj->ID() == TEMPORARY_OBJECT_ID)
848 return nullptr;
849
850 ErrorLogger() << "GeneralizedLocationType unable to locate " << obj->Name() << "(" << obj->ID() << ")";
851 return nullptr;
852 }
853
854 /** Return the location of the object with id \p object_id.*/
GeneralizedLocation(int object_id)855 GeneralizedLocationType GeneralizedLocation(int object_id) {
856 auto obj = Objects().get(object_id);
857 return GeneralizedLocation(obj);
858 }
859
860 }
861
862 /** JumpDistanceSys1Visitor and JumpDistanceSys2Visitor are variant visitors
863 that can be used to determine the distance between a pair of objects
864 locations represented as GeneralizedLocations.*/
865
866 /** JumpDistanceSys2Visitor determines the distance between \p _sys_id1 and the
867 GeneralizedLocation that it is visiting.*/
868 struct JumpDistanceSys2Visitor : public boost::static_visitor<int> {
JumpDistanceSys2VisitorJumpDistanceSys2Visitor869 JumpDistanceSys2Visitor(const Pathfinder::PathfinderImpl& _pf, int _sys_id1) :
870 pf(_pf), sys_id1(_sys_id1)
871 {}
872
873 /** Return the maximum distance if this end is nowhere. */
operator ()JumpDistanceSys2Visitor874 int operator()(std::nullptr_t) const { return INT_MAX; }
875
876 /** Simple case of two system ids, return the distance between systems.*/
operator ()JumpDistanceSys2Visitor877 int operator()(int sys_id2) const {
878 short sjumps = -1;
879 try {
880 sjumps = pf.JumpDistanceBetweenSystems(sys_id1, sys_id2);
881 } catch (const std::out_of_range&) {
882 ErrorLogger() << "JumpsBetweenObjects caught out of range exception sys_id1 = "
883 << sys_id1 << " sys_id2 = " << sys_id2;
884 return INT_MAX;
885 }
886 int jumps = (sjumps == -1) ? INT_MAX : static_cast<int>(sjumps);
887 return jumps;
888 }
889
890 /** A single system id and a fleet with two locations. For an object in
891 transit return the distance to the closest system.*/
operator ()JumpDistanceSys2Visitor892 int operator()(std::pair<int, int> prev_next) const {
893 short sjumps1 = -1, sjumps2 = -1;
894 int prev_sys_id = prev_next.first, next_sys_id = prev_next.second;
895 try {
896 if (prev_sys_id != INVALID_OBJECT_ID)
897 sjumps1 = pf.JumpDistanceBetweenSystems(sys_id1, prev_sys_id);
898 if (next_sys_id != INVALID_OBJECT_ID)
899 sjumps2 = pf.JumpDistanceBetweenSystems(sys_id1, next_sys_id);
900 } catch (...) {
901 ErrorLogger() << "JumpsBetweenObjects caught exception when calling JumpDistanceBetweenSystems";
902 }
903 int jumps1 = (sjumps1 == -1) ? INT_MAX : static_cast<int>(sjumps1);
904 int jumps2 = (sjumps2 == -1) ? INT_MAX : static_cast<int>(sjumps2);
905
906 int jumps = std::min(jumps1, jumps2);
907 return jumps;
908 }
909 const Pathfinder::PathfinderImpl& pf;
910 int sys_id1;
911 };
912
913 /** JumpDistanceSys1Visitor visits the first system and uses
914 JumpDistanceSysVisitor2 to determines the distance between \p _sys_id2 and
915 the GeneralizedLocation that it is visiting.*/
916 struct JumpDistanceSys1Visitor : public boost::static_visitor<int> {
JumpDistanceSys1VisitorJumpDistanceSys1Visitor917 JumpDistanceSys1Visitor(const Pathfinder::PathfinderImpl& _pf,
918 const GeneralizedLocationType&_sys2_ids) :
919 pf(_pf), sys2_ids(_sys2_ids)
920 {}
921
922 /** Return the maximum distance if the first object is nowhere.*/
operator ()JumpDistanceSys1Visitor923 int operator()(std::nullptr_t) const { return INT_MAX; }
924
925 /** For a single system, return the application of JumpDistanceSys2Visitor
926 to the second system.*/
operator ()JumpDistanceSys1Visitor927 int operator()(int sys_id1) const {
928 JumpDistanceSys2Visitor visitor(pf, sys_id1);
929 return boost::apply_visitor(visitor, sys2_ids);
930 }
931
932 /** For an object in transit, apply the JumpDistanceSys2Visitor and return
933 the shortest distance.*/
operator ()JumpDistanceSys1Visitor934 int operator()(std::pair<int, int> prev_next) const {
935 short sjumps1 = -1, sjumps2 = -1;
936 int prev_sys_id = prev_next.first, next_sys_id = prev_next.second;
937 if (prev_sys_id != INVALID_OBJECT_ID) {
938 JumpDistanceSys2Visitor visitor(pf, prev_sys_id);
939 sjumps1 = boost::apply_visitor(visitor, sys2_ids);
940 }
941 if (next_sys_id!= INVALID_OBJECT_ID) {
942 JumpDistanceSys2Visitor visitor(pf, next_sys_id);
943 sjumps2 = boost::apply_visitor(visitor, sys2_ids);
944 }
945
946 int jumps1 = (sjumps1 == -1) ? INT_MAX : static_cast<int>(sjumps1);
947 int jumps2 = (sjumps2 == -1) ? INT_MAX : static_cast<int>(sjumps2);
948
949 int jumps = std::min(jumps1, jumps2);
950 return jumps;
951 }
952 const Pathfinder::PathfinderImpl& pf;
953 const GeneralizedLocationType& sys2_ids;
954 };
955
JumpDistanceBetweenObjects(int object1_id,int object2_id) const956 int Pathfinder::JumpDistanceBetweenObjects(int object1_id, int object2_id) const
957 { return pimpl->JumpDistanceBetweenObjects(object1_id, object2_id); }
958
JumpDistanceBetweenObjects(int object1_id,int object2_id) const959 int Pathfinder::PathfinderImpl::JumpDistanceBetweenObjects(int object1_id, int object2_id) const {
960 GeneralizedLocationType obj1 = GeneralizedLocation(object1_id);
961 GeneralizedLocationType obj2 = GeneralizedLocation(object2_id);
962 JumpDistanceSys1Visitor visitor(*this, obj2);
963 return boost::apply_visitor(visitor, obj1);
964 }
965
ShortestPath(int system1_id,int system2_id,int empire_id) const966 std::pair<std::list<int>, double> Pathfinder::ShortestPath(int system1_id, int system2_id,
967 int empire_id/* = ALL_EMPIRES*/) const
968 { return pimpl->ShortestPath(system1_id, system2_id, empire_id); }
969
ShortestPath(int system1_id,int system2_id,int empire_id) const970 std::pair<std::list<int>, double> Pathfinder::PathfinderImpl::ShortestPath(int system1_id, int system2_id,
971 int empire_id/* = ALL_EMPIRES*/) const
972 {
973 if (empire_id == ALL_EMPIRES) {
974 // find path on full / complete system graph
975 try {
976 double linear_distance = LinearDistance(system1_id, system2_id);
977 return ShortestPathImpl(m_graph_impl->system_graph, system1_id, system2_id,
978 linear_distance, m_system_id_to_graph_index);
979 } catch (const std::out_of_range&) {
980 ErrorLogger() << "PathfinderImpl::ShortestPath passed invalid system id(s): "
981 << system1_id << " & " << system2_id;
982 throw;
983 }
984 }
985
986 // find path on single empire's view of system graph
987 auto graph_it = m_graph_impl->empire_system_graph_views.find(empire_id);
988 if (graph_it == m_graph_impl->empire_system_graph_views.end()) {
989 ErrorLogger() << "PathfinderImpl::ShortestPath passed unknown empire id: " << empire_id;
990 throw std::out_of_range("PathfinderImpl::ShortestPath passed unknown empire id");
991 }
992 try {
993 double linear_distance = LinearDistance(system1_id, system2_id);
994 return ShortestPathImpl(*graph_it->second, system1_id, system2_id,
995 linear_distance, m_system_id_to_graph_index);
996 } catch (const std::out_of_range&) {
997 ErrorLogger() << "PathfinderImpl::ShortestPath passed invalid system id(s): "
998 << system1_id << " & " << system2_id;
999 return {std::list<int>(), -1.0};
1000 }
1001 }
1002
ShortestPath(int system1_id,int system2_id,int empire_id,const SystemExclusionPredicateType & system_predicate) const1003 std::pair<std::list<int>, double> Pathfinder::ShortestPath(int system1_id, int system2_id, int empire_id,
1004 const SystemExclusionPredicateType& system_predicate) const
1005 { return pimpl->ShortestPath(system1_id, system2_id, empire_id, system_predicate); }
1006
ShortestPath(int system1_id,int system2_id,int empire_id,const Pathfinder::SystemExclusionPredicateType & sys_pred) const1007 std::pair<std::list<int>, double> Pathfinder::PathfinderImpl::ShortestPath(
1008 int system1_id, int system2_id, int empire_id, const Pathfinder::SystemExclusionPredicateType& sys_pred) const
1009 {
1010 if (empire_id == ALL_EMPIRES) {
1011 ErrorLogger() << "Invalid empire " << empire_id;
1012 throw std::out_of_range("PathfinderImpl::ShortestPath passed invalid empire id");
1013 }
1014
1015 auto func_it = m_graph_impl->system_pred_graph_views.find(sys_pred);
1016 if (func_it == m_graph_impl->system_pred_graph_views.end()) {
1017 m_graph_impl->AddSystemPredicate(sys_pred);
1018 func_it = m_graph_impl->system_pred_graph_views.find(sys_pred);
1019 if (func_it == m_graph_impl->system_pred_graph_views.end()) {
1020 ErrorLogger() << "No graph views found for predicate";
1021 throw std::out_of_range("PathfinderImpl::ShortestPath No graph views found for predicate");
1022 }
1023 }
1024 auto graph_it = func_it->second.find(empire_id);
1025 if (graph_it == func_it->second.end()) {
1026 ErrorLogger() << "No graph view found for empire " << empire_id;
1027 throw std::out_of_range("PathfinderImpl::ShortestPath No graph view for empire");
1028 }
1029
1030 try {
1031 auto linear_distance = LinearDistance(system1_id, system2_id);
1032 return ShortestPathImpl(*graph_it->second, system1_id, system2_id,
1033 linear_distance, m_system_id_to_graph_index);
1034 } catch (const std::out_of_range&) {
1035 ErrorLogger() << "Invalid system id(s): " << system1_id << ", " << system2_id;
1036 throw;
1037 }
1038 }
1039
ShortestPathDistance(int object1_id,int object2_id) const1040 double Pathfinder::ShortestPathDistance(int object1_id, int object2_id) const {
1041 return pimpl->ShortestPathDistance(object1_id, object2_id);
1042 }
1043
ShortestPathDistance(int object1_id,int object2_id) const1044 double Pathfinder::PathfinderImpl::ShortestPathDistance(int object1_id, int object2_id) const {
1045 // If one or both objects are (in) a fleet between systems, use the destination system
1046 // and add the distance from the fleet to the destination system, essentially calculating
1047 // the distance travelled until both could be in the same system.
1048 const auto obj1 = Objects().get(object1_id);
1049 if (!obj1)
1050 return -1;
1051
1052 const auto obj2 = Objects().get(object2_id);
1053 if (!obj2)
1054 return -1;
1055
1056 auto system_one = Objects().get<System>(obj1->SystemID());
1057 auto system_two = Objects().get<System>(obj2->SystemID());
1058 std::pair< std::list< int >, double > path_len_pair;
1059 double dist1(0.0), dist2(0.0);
1060 std::shared_ptr<const Fleet> fleet;
1061
1062 if (!system_one) {
1063 fleet = FleetFromObject(obj1);
1064 if (!fleet)
1065 return -1;
1066 if (auto next_sys = Objects().get<System>(fleet->NextSystemID())) {
1067 system_one = next_sys;
1068 dist1 = std::sqrt(pow((next_sys->X() - fleet->X()), 2) + pow((next_sys->Y() - fleet->Y()), 2));
1069 }
1070 }
1071
1072 if (!system_two) {
1073 fleet = FleetFromObject(obj2);
1074 if (!fleet)
1075 return -1;
1076 if (auto next_sys = Objects().get<System>(fleet->NextSystemID())) {
1077 system_two = next_sys;
1078 dist2 = std::sqrt(pow((next_sys->X() - fleet->X()), 2) + pow((next_sys->Y() - fleet->Y()), 2));
1079 }
1080 }
1081
1082 try {
1083 path_len_pair = ShortestPath(system_one->ID(), system_two->ID());
1084 } catch (...) {
1085 ErrorLogger() << "ShortestPathDistance caught exception when calling ShortestPath";
1086 return -1;
1087 }
1088 return path_len_pair.second + dist1 + dist2;
1089 }
1090
LeastJumpsPath(int system1_id,int system2_id,int empire_id,int max_jumps) const1091 std::pair<std::list<int>, int> Pathfinder::LeastJumpsPath(
1092 int system1_id, int system2_id, int empire_id/* = ALL_EMPIRES*/, int max_jumps/* = INT_MAX*/) const {
1093 return pimpl->LeastJumpsPath(system1_id, system2_id, empire_id, max_jumps);
1094 }
1095
LeastJumpsPath(int system1_id,int system2_id,int empire_id,int max_jumps) const1096 std::pair<std::list<int>, int> Pathfinder::PathfinderImpl::LeastJumpsPath(
1097 int system1_id, int system2_id, int empire_id/* = ALL_EMPIRES*/, int max_jumps/* = INT_MAX*/) const
1098 {
1099 if (empire_id == ALL_EMPIRES) {
1100 // find path on full / complete system graph
1101 try {
1102 return LeastJumpsPathImpl(m_graph_impl->system_graph, system1_id, system2_id,
1103 m_system_id_to_graph_index, max_jumps);
1104 } catch (const std::out_of_range&) {
1105 ErrorLogger() << "PathfinderImpl::LeastJumpsPath passed invalid system id(s): "
1106 << system1_id << " & " << system2_id;
1107 throw;
1108 }
1109 }
1110
1111 // find path on single empire's view of system graph
1112 auto graph_it = m_graph_impl->empire_system_graph_views.find(empire_id);
1113 if (graph_it == m_graph_impl->empire_system_graph_views.end()) {
1114 ErrorLogger() << "PathfinderImpl::LeastJumpsPath passed unknown empire id: " << empire_id;
1115 throw std::out_of_range("PathfinderImpl::LeastJumpsPath passed unknown empire id");
1116 }
1117 try {
1118 return LeastJumpsPathImpl(*graph_it->second, system1_id, system2_id,
1119 m_system_id_to_graph_index, max_jumps);
1120 } catch (const std::out_of_range&) {
1121 ErrorLogger() << "PathfinderImpl::LeastJumpsPath passed invalid system id(s): "
1122 << system1_id << " & " << system2_id;
1123 throw;
1124 }
1125 }
1126
SystemsConnected(int system1_id,int system2_id,int empire_id) const1127 bool Pathfinder::SystemsConnected(int system1_id, int system2_id, int empire_id) const
1128 { return pimpl->SystemsConnected(system1_id, system2_id, empire_id); }
1129
SystemsConnected(int system1_id,int system2_id,int empire_id) const1130 bool Pathfinder::PathfinderImpl::SystemsConnected(int system1_id, int system2_id, int empire_id) const {
1131 TraceLogger() << "SystemsConnected(" << system1_id << ", " << system2_id << ", " << empire_id << ")";
1132 auto path = LeastJumpsPath(system1_id, system2_id, empire_id);
1133 TraceLogger() << "SystemsConnected returned path of size: " << path.first.size();
1134 bool retval = !path.first.empty();
1135 TraceLogger() << "SystemsConnected retval: " << retval;
1136 return retval;
1137 }
1138
SystemHasVisibleStarlanes(int system_id,int empire_id) const1139 bool Pathfinder::SystemHasVisibleStarlanes(int system_id, int empire_id) const
1140 { return pimpl->SystemHasVisibleStarlanes(system_id, empire_id); }
1141
SystemHasVisibleStarlanes(int system_id,int empire_id) const1142 bool Pathfinder::PathfinderImpl::SystemHasVisibleStarlanes(int system_id, int empire_id) const {
1143 if (auto system = EmpireKnownObjects(empire_id).get<System>(system_id))
1144 if (!system->StarlanesWormholes().empty())
1145 return true;
1146 return false;
1147 }
1148
ImmediateNeighbors(int system_id,int empire_id) const1149 std::multimap<double, int> Pathfinder::ImmediateNeighbors(int system_id, int empire_id/* = ALL_EMPIRES*/) const
1150 { return pimpl->ImmediateNeighbors(system_id, empire_id); }
1151
ImmediateNeighbors(int system_id,int empire_id) const1152 std::multimap<double, int> Pathfinder::PathfinderImpl::ImmediateNeighbors(
1153 int system_id, int empire_id/* = ALL_EMPIRES*/) const
1154 {
1155 if (empire_id == ALL_EMPIRES) {
1156 return ImmediateNeighborsImpl(m_graph_impl->system_graph, system_id,
1157 m_system_id_to_graph_index);
1158 } else {
1159 auto graph_it = m_graph_impl->empire_system_graph_views.find(empire_id);
1160 if (graph_it != m_graph_impl->empire_system_graph_views.end())
1161 return ImmediateNeighborsImpl(*graph_it->second, system_id,
1162 m_system_id_to_graph_index);
1163 }
1164 return std::multimap<double, int>();
1165 }
1166
1167
WithinJumpsCacheHit(std::unordered_set<int> * result,size_t jump_limit,size_t ii,distance_matrix_storage<short>::row_ref row) const1168 void Pathfinder::PathfinderImpl::WithinJumpsCacheHit(
1169 std::unordered_set<int>* result, size_t jump_limit,
1170 size_t ii, distance_matrix_storage<short>::row_ref row) const
1171 {
1172 // Scan the LUT of system ids and add any result from the row within
1173 // the neighborhood range to the results.
1174 for (auto system_id_and_ii : m_system_id_to_graph_index) {
1175 size_t hops = row[system_id_and_ii.second];
1176 if (hops <= jump_limit)
1177 result->insert(system_id_and_ii.first);
1178 }
1179 }
1180
WithinJumps(size_t jumps,const std::vector<int> & candidates) const1181 std::unordered_set<int> Pathfinder::WithinJumps(size_t jumps, const std::vector<int>& candidates) const
1182 { return pimpl->WithinJumps(jumps, candidates); }
1183
WithinJumps(size_t jumps,const std::vector<int> & candidates) const1184 std::unordered_set<int> Pathfinder::PathfinderImpl::WithinJumps(
1185 size_t jumps, const std::vector<int>& candidates) const
1186 {
1187 std::unordered_set<int> near;
1188 distance_matrix_cache< distance_matrix_storage<short>> cache(m_system_jumps);
1189 for (auto candidate : candidates) {
1190 size_t system_index;
1191 try {
1192 system_index = m_system_id_to_graph_index.at(candidate);
1193 } catch (const std::out_of_range& e) {
1194 ErrorLogger() << "Passed invalid system id: " << candidate;
1195 continue;
1196 }
1197
1198 near.insert(candidate);
1199 if (jumps == 0)
1200 continue;
1201
1202 #if BOOST_VERSION >= 106000
1203 using boost::placeholders::_1;
1204 using boost::placeholders::_2;
1205 #endif
1206
1207 cache.examine_row(system_index,
1208 boost::bind(&Pathfinder::PathfinderImpl::HandleCacheMiss, this, _1, _2),
1209 boost::bind(&Pathfinder::PathfinderImpl::WithinJumpsCacheHit, this,
1210 &near, jumps, _1, _2));
1211 }
1212 return near;
1213 }
1214
1215
1216
1217 /** Examine a single universe object and determine if it is within jumps
1218 of any object in others.*/
1219 struct WithinJumpsOfOthersObjectVisitor : public boost::static_visitor<bool> {
WithinJumpsOfOthersObjectVisitorWithinJumpsOfOthersObjectVisitor1220 WithinJumpsOfOthersObjectVisitor(const Pathfinder::PathfinderImpl& _pf,
1221 int _jumps,
1222 const std::vector<std::shared_ptr<const UniverseObject>>& _others) :
1223 pf(_pf), jumps(_jumps), others(_others)
1224 {}
1225
operator ()WithinJumpsOfOthersObjectVisitor1226 bool operator()(std::nullptr_t) const { return false; }
operator ()WithinJumpsOfOthersObjectVisitor1227 bool operator()(int sys_id) const {
1228 bool retval = pf.WithinJumpsOfOthers(jumps, sys_id, others);
1229 return retval;
1230 }
operator ()WithinJumpsOfOthersObjectVisitor1231 bool operator()(std::pair<int, int> prev_next) const {
1232 return pf.WithinJumpsOfOthers(jumps, prev_next.first, others)
1233 || pf.WithinJumpsOfOthers(jumps, prev_next.second, others);
1234 }
1235 const Pathfinder::PathfinderImpl& pf;
1236 int jumps;
1237 const std::vector<std::shared_ptr<const UniverseObject>>& others;
1238 };
1239
1240 /** Examine a single other in the cache to see if any of its locations
1241 are within jumps*/
1242 struct WithinJumpsOfOthersOtherVisitor : public boost::static_visitor<bool> {
WithinJumpsOfOthersOtherVisitorWithinJumpsOfOthersOtherVisitor1243 WithinJumpsOfOthersOtherVisitor(const Pathfinder::PathfinderImpl& _pf,
1244 int _jumps,
1245 distance_matrix_storage<short>::row_ref _row) :
1246 pf(_pf), jumps(_jumps), row(_row)
1247 {}
1248
single_resultWithinJumpsOfOthersOtherVisitor1249 bool single_result(int other_id) const {
1250 int index;
1251 try {
1252 index = pf.m_system_id_to_graph_index.at(other_id);
1253 } catch (const std::out_of_range& e) {
1254 ErrorLogger() << "Passed invalid system id: " << other_id;
1255 return false;
1256 }
1257 bool retval = (row[index] <= jumps);
1258 return retval;
1259 }
1260
operator ()WithinJumpsOfOthersOtherVisitor1261 bool operator()(std::nullptr_t) const { return false; }
operator ()WithinJumpsOfOthersOtherVisitor1262 bool operator()(int sys_id) const {
1263 return single_result(sys_id);
1264 }
operator ()WithinJumpsOfOthersOtherVisitor1265 bool operator()(std::pair<int, int> prev_next) const {
1266 return single_result(prev_next.first)
1267 || single_result(prev_next.second);
1268 }
1269 const Pathfinder::PathfinderImpl& pf;
1270 int jumps;
1271 distance_matrix_storage<short>::row_ref row;
1272 };
1273
1274
WithinJumpsOfOthersCacheHit(bool & answer,int jumps,const std::vector<std::shared_ptr<const UniverseObject>> & others,size_t ii,distance_matrix_storage<short>::row_ref row) const1275 void Pathfinder::PathfinderImpl::WithinJumpsOfOthersCacheHit(
1276 bool& answer, int jumps,
1277 const std::vector<std::shared_ptr<const UniverseObject>>& others,
1278 size_t ii, distance_matrix_storage<short>::row_ref row) const
1279 {
1280 // Check if any of the others are within jumps of candidate, by looping
1281 // through all of the others and applying the WithinJumpsOfOthersOtherVisitor.
1282 answer = false;
1283 for (const auto& other : others) {
1284 WithinJumpsOfOthersOtherVisitor check_if_location_is_within_jumps(*this, jumps, row);
1285 GeneralizedLocationType location = GeneralizedLocation(other);
1286 if (boost::apply_visitor(check_if_location_is_within_jumps, location)) {
1287 answer = true;
1288 return;
1289 }
1290 }
1291 }
1292
1293 std::pair<std::vector<std::shared_ptr<const UniverseObject>>,
1294 std::vector<std::shared_ptr<const UniverseObject>>>
WithinJumpsOfOthers(int jumps,const std::vector<std::shared_ptr<const UniverseObject>> & candidates,const std::vector<std::shared_ptr<const UniverseObject>> & stationary) const1295 Pathfinder::WithinJumpsOfOthers(
1296 int jumps,
1297 const std::vector<std::shared_ptr<const UniverseObject>>& candidates,
1298 const std::vector<std::shared_ptr<const UniverseObject>>& stationary) const
1299 {
1300 return pimpl->WithinJumpsOfOthers(jumps, candidates, stationary);
1301 }
1302
1303 std::pair<std::vector<std::shared_ptr<const UniverseObject>>,
1304 std::vector<std::shared_ptr<const UniverseObject>>>
WithinJumpsOfOthers(int jumps,const std::vector<std::shared_ptr<const UniverseObject>> & candidates,const std::vector<std::shared_ptr<const UniverseObject>> & stationary) const1305 Pathfinder::PathfinderImpl::WithinJumpsOfOthers(
1306 int jumps,
1307 const std::vector<std::shared_ptr<const UniverseObject>>& candidates,
1308 const std::vector<std::shared_ptr<const UniverseObject>>& stationary) const
1309 {
1310 // Examine each candidate and copy those within jumps of the
1311 // others into near and the rest into far.
1312 WithinJumpsOfOthersObjectVisitor visitor(*this, jumps, stationary);
1313 std::vector<std::shared_ptr<const UniverseObject>> near, far;
1314 size_t size = candidates.size();
1315 near.reserve(size);
1316 far.reserve(size);
1317
1318 for (const auto& candidate : candidates) {
1319 GeneralizedLocationType candidate_systems = GeneralizedLocation(candidate);
1320 bool is_near = boost::apply_visitor(visitor, candidate_systems);
1321
1322 if (is_near)
1323 near.push_back(candidate);
1324 else
1325 far.push_back(candidate);
1326 }
1327
1328 return {near, far}; //, wherever you are...
1329 }
1330
WithinJumpsOfOthers(int jumps,int system_id,const std::vector<std::shared_ptr<const UniverseObject>> & others) const1331 bool Pathfinder::PathfinderImpl::WithinJumpsOfOthers(
1332 int jumps, int system_id,
1333 const std::vector<std::shared_ptr<const UniverseObject>>& others) const
1334 {
1335 if (others.empty())
1336 return false;
1337
1338 size_t system_index;
1339 try {
1340 system_index = m_system_id_to_graph_index.at(system_id);
1341 } catch (const std::out_of_range& e) {
1342 ErrorLogger() << "Passed invalid system id: " << system_id;
1343 return false;
1344 }
1345
1346 #if BOOST_VERSION >= 106000
1347 using boost::placeholders::_1;
1348 using boost::placeholders::_2;
1349 #endif
1350
1351 // Examine the cache to see if \p system_id is within \p jumps of \p others
1352 bool within_jumps(false);
1353 distance_matrix_cache<distance_matrix_storage<short>> cache(m_system_jumps);
1354 cache.examine_row(system_index,
1355 boost::bind(&Pathfinder::PathfinderImpl::HandleCacheMiss, this, _1, _2),
1356 boost::bind(&Pathfinder::PathfinderImpl::WithinJumpsOfOthersCacheHit, this,
1357 std::ref(within_jumps), jumps, others, _1, _2));
1358 return within_jumps;
1359 }
1360
NearestSystemTo(double x,double y) const1361 int Pathfinder::NearestSystemTo(double x, double y) const
1362 { return pimpl->NearestSystemTo(x, y); }
1363
NearestSystemTo(double x,double y) const1364 int Pathfinder::PathfinderImpl::NearestSystemTo(double x, double y) const {
1365 double min_dist2 = std::numeric_limits<double>::max();
1366 int min_dist2_sys_id = INVALID_OBJECT_ID;
1367
1368 auto systems = Objects().all<System>();
1369
1370 for (auto const& system : systems) {
1371 double xs = system->X();
1372 double ys = system->Y();
1373 double dist2 = (xs-x)*(xs-x) + (ys-y)*(ys-y);
1374 if (dist2 == 0.0) {
1375 return system->ID();
1376 } else if (dist2 < min_dist2) {
1377 min_dist2 = dist2;
1378 min_dist2_sys_id = system->ID();
1379 }
1380 }
1381 return min_dist2_sys_id;
1382 }
1383
1384
InitializeSystemGraph(const std::vector<int> system_ids,int for_empire_id)1385 void Pathfinder::InitializeSystemGraph(const std::vector<int> system_ids, int for_empire_id)
1386 { return pimpl->InitializeSystemGraph(system_ids, for_empire_id); }
1387
InitializeSystemGraph(const std::vector<int> system_ids,int for_empire_id)1388 void Pathfinder::PathfinderImpl::InitializeSystemGraph(
1389 const std::vector<int> system_ids, int for_empire_id)
1390 {
1391 auto new_graph_impl = std::make_shared<GraphImpl>();
1392 // auto system_ids = ::EmpireKnownObjects(for_empire_id).FindObjectIDs<System>();
1393 // NOTE: this initialization of graph_changed prevents testing for edges between nonexistant vertices
1394 bool graph_changed = system_ids.size() != boost::num_vertices(m_graph_impl->system_graph);
1395
1396 auto ints_to_string = [](const std::vector<int>& ints_vec) {
1397 std::stringstream o;
1398 for (auto id : ints_vec)
1399 o << id << " ";
1400 return o.str();
1401 };
1402 TraceLogger() << "InitializeSystemGraph(" << for_empire_id
1403 << ") system_ids: (" << system_ids.size() << "): "
1404 << ints_to_string(system_ids);
1405
1406 GraphImpl::SystemIDPropertyMap sys_id_property_map =
1407 boost::get(vertex_system_id_t(), new_graph_impl->system_graph);
1408
1409 GraphImpl::EdgeWeightPropertyMap edge_weight_map =
1410 boost::get(boost::edge_weight, new_graph_impl->system_graph);
1411
1412 const GraphImpl::EdgeWeightPropertyMap& current_edge_weight_map =
1413 boost::get(boost::edge_weight, m_graph_impl->system_graph);
1414
1415 // add vertices to graph for all systems
1416 for (size_t system_index = 0; system_index < system_ids.size(); ++system_index) {
1417 // add a vertex to the graph for this system, and assign it the system's universe ID as a property
1418 boost::add_vertex(new_graph_impl->system_graph);
1419 int system_id = system_ids[system_index];
1420 sys_id_property_map[system_index] = system_id;
1421 // add record of index in new_graph_impl->system_graph of this system
1422 m_system_id_to_graph_index[system_id] = system_index;
1423 }
1424
1425 // add edges for all starlanes
1426 for (size_t system1_index = 0; system1_index < system_ids.size(); ++system1_index) {
1427 int system1_id = system_ids[system1_index];
1428 std::shared_ptr<const System> system1 = EmpireKnownObjects(for_empire_id).get<System>(system1_id);
1429 //std::shared_ptr<const System> & system1 = systems[system1_index];
1430
1431 // add edges and edge weights
1432 for (auto const& lane_dest : system1->StarlanesWormholes()) {
1433 // get id in universe of system at other end of lane
1434 const int lane_dest_id = lane_dest.first;
1435 // skip null lanes and only add edges in one direction, to avoid
1436 // duplicating edges ( since this is an undirected graph, A->B
1437 // duplicates B->A )
1438 if (lane_dest_id >= system1_id)
1439 continue;
1440
1441 // get new_graph_impl->system_graph index for this system
1442 auto reverse_lookup_map_it = m_system_id_to_graph_index.find(lane_dest_id);
1443 if (reverse_lookup_map_it == m_system_id_to_graph_index.end())
1444 continue; // couldn't find destination system id in vertex lookup map; don't add to graph
1445 size_t lane_dest_graph_index = reverse_lookup_map_it->second;
1446
1447 auto add_edge_result = boost::add_edge(system1_index, lane_dest_graph_index,
1448 new_graph_impl->system_graph);
1449
1450 if (add_edge_result.second) { // if this is a non-duplicate starlane or wormhole
1451 if (lane_dest.second) { // if this is a wormhole
1452 edge_weight_map[add_edge_result.first] = WORMHOLE_TRAVEL_DISTANCE;
1453 } else { // if this is a starlane
1454 edge_weight_map[add_edge_result.first] = LinearDistance(system1_id, lane_dest_id);
1455 }
1456
1457 if (!graph_changed) {
1458 const auto maybe_current_edge = boost::edge(system1_index, lane_dest_graph_index, m_graph_impl->system_graph);
1459 // Does the current edge exist with the same weight in the old graph
1460 graph_changed = (!maybe_current_edge.second
1461 || (edge_weight_map[add_edge_result.first]
1462 != current_edge_weight_map[maybe_current_edge.first]));
1463 }
1464 }
1465 }
1466 }
1467
1468 // if all previous edges still exist in the new graph, and the number of vertices and edges hasn't changed,
1469 // then no vertices or edges can have been added either, so it is still the same graph
1470 graph_changed = graph_changed ||
1471 boost::num_edges(new_graph_impl->system_graph) !=
1472 boost::num_edges(m_graph_impl->system_graph);
1473
1474 if (graph_changed) {
1475 new_graph_impl.swap(m_graph_impl);
1476 // clear jumps distance cache
1477 // NOTE: re-filling the cache is O(#vertices * (#vertices + #edges)) in the worst case!
1478 m_system_jumps.resize(system_ids.size());
1479 }
1480 UpdateEmpireVisibilityFilteredSystemGraphs(for_empire_id);
1481 }
1482
UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id)1483 void Pathfinder::UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id)
1484 { return pimpl->UpdateEmpireVisibilityFilteredSystemGraphs(for_empire_id); }
1485
UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id)1486 void Pathfinder::PathfinderImpl::UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id) {
1487 m_graph_impl->empire_system_graph_views.clear();
1488 m_graph_impl->system_pred_graph_views.clear();
1489
1490 // if building system graph views for all empires, then each empire's graph
1491 // should accurately filter for that empire's visibility. if building
1492 // graphs for one empire, that empire won't know what systems other empires
1493 // have visibility of, so instead, have all empires' filtered graphs be
1494 // equal to the empire for which filtering is being done. this way, on the
1495 // clients, enemy fleets can have move paths even though the client doesn't
1496 // know what systems those empires know about (so can't make an accurate
1497 // filtered graph for other empires)
1498
1499 if (for_empire_id == ALL_EMPIRES) {
1500 // all empires get their own, accurately filtered graph
1501 for (auto const& empire : Empires()) {
1502 int empire_id = empire.first;
1503 GraphImpl::EdgeVisibilityFilter filter(&m_graph_impl->system_graph, empire_id);
1504 auto filtered_graph_ptr = std::make_shared<GraphImpl::EmpireViewSystemGraph>(m_graph_impl->system_graph, filter);
1505 m_graph_impl->empire_system_graph_views[empire_id] = filtered_graph_ptr;
1506 }
1507
1508 } else {
1509 // all empires share a single filtered graph, filtered by the for_empire_id
1510 GraphImpl::EdgeVisibilityFilter filter(&m_graph_impl->system_graph, for_empire_id);
1511 auto filtered_graph_ptr = std::make_shared<GraphImpl::EmpireViewSystemGraph>(m_graph_impl->system_graph, filter);
1512
1513 for (auto const& empire : Empires()) {
1514 int empire_id = empire.first;
1515 m_graph_impl->empire_system_graph_views[empire_id] = filtered_graph_ptr;
1516 }
1517 }
1518 for (const auto& prev_pred : m_graph_impl->system_predicates)
1519 m_graph_impl->AddSystemPredicate(prev_pred);
1520 }
1521