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