]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/route.cpp
Add support for named zones
[r2c2.git] / source / libr2c2 / route.cpp
1 /* $Id$
2
3 This file is part of R²C²
4 Copyright © 2007-2010  Mikkosoft Productions, Mikko Rasa
5 Distributed under the GPL
6 */
7
8 #include <queue>
9 #include <msp/strings/formatter.h>
10 #include "layout.h"
11 #include "route.h"
12 #include "track.h"
13 #include "trackiter.h"
14 #include "tracktype.h"
15 #include "zone.h"
16
17 using namespace std;
18 using namespace Msp;
19
20 namespace {
21
22 using namespace R2C2;
23
24 typedef std::pair<Track *, unsigned> Key;
25
26 struct Node
27 {
28         TrackIter track;
29         Node *prev;
30         float dist;
31
32         Node():
33                 prev(0), dist(0)
34         { }
35
36         Node(const TrackIter &t):
37                 track(t), prev(0), dist(0)
38         { }
39
40         Node(const TrackIter &t, Node &r, float d):
41                 track(t), prev(&r), dist(prev->dist+d)
42         { }
43
44         bool operator<(const Node &other) const
45         { return dist>other.dist; }
46 };
47
48 struct TrackMatch
49 {
50         Track &track;
51
52         TrackMatch(Track &t): track(t) { }
53
54         bool operator()(Track &t) const { return &t==&track; }
55 };
56
57 struct TrackInSet
58 {
59         const set<Track *> &tracks;
60
61         TrackInSet(const set<Track *> &t): tracks(t) { }
62
63         bool operator()(Track &t) const { return tracks.count(&t); }
64 };
65
66 template<typename Pred>
67 list<Track *> dijkstra(const TrackIter &from, const Pred &goal)
68 {
69         map<Key, Node> track_nodes;
70         priority_queue<Node> nodes;
71         Node *final = 0;
72
73         nodes.push(from);
74
75         while(!nodes.empty())
76         {
77                 Node lowest = nodes.top();
78                 nodes.pop();
79
80                 Key key(lowest.track.track(), lowest.track.entry());
81                 if(track_nodes.count(key))
82                         continue;
83
84                 Node &ref = track_nodes[key] = lowest;
85                 if(goal(*lowest.track))
86                 {
87                         final = &ref;
88                         break;
89                 }
90
91                 unsigned paths = lowest.track.endpoint().paths;
92                 for(unsigned i=0; paths>>i; ++i)
93                         if(paths&(1<<i))
94                         {
95                                 TrackIter next = lowest.track.next(i);
96                                 if(!next)
97                                         continue;
98
99                                 if(track_nodes.count(Key(next.track(), next.entry())))
100                                         continue;
101
102                                 nodes.push(Node(next, ref, lowest.track->get_type().get_path_length(i)));
103                         }
104         }
105
106         list<Track *> result;
107         for(Node *node=final; node; node=node->prev)
108                 result.push_front(&*node->track);
109
110         return result;
111 }
112
113 template<typename Pred>
114 Route *create_route(const TrackIter &from, const Pred &goal)
115 {
116         list<Track *> tracks = dijkstra(from, goal);
117
118         if(tracks.empty())
119                 return 0;
120
121         Route *route = new Route(from->get_layout());
122         for(list<Track *>::iterator i=tracks.begin(); i!=tracks.end(); ++i)
123                 route->add_track(**i);
124
125         route->set_name("Pathfinder");
126         route->set_temporary(true);
127
128         return route;
129 }
130
131 }
132
133
134 namespace R2C2 {
135
136 Route::Route(Layout &l):
137         layout(l),
138         temporary(false)
139 {
140         layout.add_route(*this);
141         layout.signal_track_removed.connect(sigc::mem_fun(this, &Route::track_removed));
142 }
143
144 Route::~Route()
145 {
146         layout.remove_route(*this);
147 }
148
149 void Route::set_name(const string &n)
150 {
151         name = n;
152         signal_name_changed.emit(name);
153 }
154
155 void Route::set_temporary(bool t)
156 {
157         temporary = t;
158 }
159
160 void Route::set_turnout(unsigned addr, unsigned path)
161 {
162         if(!addr)
163                 throw InvalidParameterValue("Invalid turnout address");
164         map<unsigned, int>::iterator i = turnouts.find(addr);
165         if(i==turnouts.end())
166                 throw KeyError("Turnout is not in this route");
167         if(i->second>=0 && path!=static_cast<unsigned>(i->second))
168                 throw InvalidState("Setting conflicts with route");
169         i->second = path;
170 }
171
172 void Route::update_turnouts()
173 {
174         set<unsigned> found;
175         for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
176                 if(unsigned tid = (*i)->get_turnout_id())
177                 {
178                         found.insert(tid);
179
180                         const vector<TrackType::Endpoint> &endpoints = (*i)->get_type().get_endpoints();
181                         const vector<Track *> &links = (*i)->get_links();
182
183                         // Build a combined path mask from linked endpoints
184                         unsigned mask = (*i)->get_type().get_paths();
185                         for(unsigned j=0; j<endpoints.size(); ++j)
186                         {
187                                 if(!tracks.count(links[j]))
188                                         continue;
189
190                                 if(unsigned tid2 = links[j]->get_turnout_id())
191                                 {
192                                         const TrackType::Endpoint &ep = links[j]->get_type().get_endpoint(links[j]->get_endpoint_by_link(**i));
193                                         int p = get_turnout(tid2);
194                                         if(p>=0 && !(ep.paths&(1<<p)))
195                                         {
196                                                 // The linked track is a turnout and has a path which is incompatible with this endpoint
197                                                 mask &= ~endpoints[j].paths;
198                                                 continue;
199                                         }
200                                 }
201                                 mask &= endpoints[j].paths;
202                         }
203
204                         if(mask && !(mask&(mask-1)))
205                         {
206                                 // Exactly one possible choice, set the path accordingly
207                                 unsigned path = 0;
208                                 for(; (mask && !(mask&1)); mask>>=1, ++path) ;
209                                 turnouts[tid] = path;
210                         }
211                         else if(!turnouts.count(tid))
212                                 // More than one possible choice, and no existing entry - set as undecided
213                                 turnouts[tid] = -1;
214                 }
215
216         // Remove any turnouts that do not exist in the route
217         for(map<unsigned, int>::iterator i=turnouts.begin(); i!=turnouts.end();)
218         {
219                 if(!found.count(i->first))
220                         turnouts.erase(i++);
221                 else
222                         ++i;
223         }
224 }
225
226 int Route::get_turnout(unsigned id) const
227 {
228         map<unsigned, int>::const_iterator i = turnouts.find(id);
229         if(i!=turnouts.end())
230                 return i->second;
231         return -1;
232 }
233
234 unsigned Route::get_path(Track &trk) const
235 {
236         if(unsigned tid = trk.get_turnout_id())
237         {
238                 map<unsigned, int>::const_iterator i = turnouts.find(tid);
239                 if(i!=turnouts.end())
240                         return i->second;
241         }
242         return trk.get_active_path();
243 }
244
245 void Route::add_track(Track &trk)
246 {
247         if(tracks.count(&trk))
248                 return;
249
250         if(!tracks.empty())
251         {
252                 unsigned valid = check_validity(trk);
253                 if(!(valid&1))
254                         throw Exception("Not linked to existing tracks");
255                 else if(!(valid&2))
256                         throw Exception("Branching routes not allowed");
257                 else if(!(valid&4))
258                         throw Exception("Route must be smooth");
259         }
260
261         tracks.insert(&trk);
262         update_turnouts();
263 }
264
265 void Route::add_tracks(const set<Track *> &trks)
266 {
267         set<Track *> pending;
268         for(set<Track *>::const_iterator i=trks.begin(); i!=trks.end(); ++i)
269                 if(!tracks.count(*i))
270                         pending.insert(*i);
271
272         while(!pending.empty())
273         {
274                 bool found = false;
275                 for(set<Track *>::const_iterator i=pending.begin(); i!=pending.end(); ++i)
276                         if(tracks.empty() || check_validity(**i)==7)
277                         {
278                                 tracks.insert(*i);
279                                 pending.erase(*i);
280                                 found = true;
281                                 break;
282                         }
283
284                 if(!found)
285                         throw Exception("Could not add all tracks to route");
286         }
287
288         update_turnouts();
289 }
290
291 void Route::add_track_chain(Track &start, unsigned ep, const TurnoutMap &trnts)
292 {
293         TrackIter iter(&start, ep);
294         while(iter)
295         {
296                 if(iter->get_type().is_dead_end())
297                         break;
298
299                 if(has_track(*iter))
300                         break;
301
302                 int path = 0;
303                 if(iter->get_turnout_id())
304                 {
305                         TurnoutMap::const_iterator i = trnts.find(iter->get_turnout_id());
306                         if(i==trnts.end())
307                                 break;
308
309                         path = i->second;
310                 }
311
312                 add_track(*iter);
313
314                 iter = iter.next(path);
315         }
316 }
317
318 bool Route::has_track(Track &t) const
319 {
320         return tracks.count(&t);
321 }
322
323 void Route::save(list<DataFile::Statement> &st) const
324 {
325         st.push_back((DataFile::Statement("name"), name));
326         for(map<unsigned, int>::const_iterator i=turnouts.begin(); i!=turnouts.end(); ++i)
327                 st.push_back((DataFile::Statement("turnout"), i->first, i->second));
328 }
329
330 unsigned Route::check_validity(Track &trk) const
331 {
332         unsigned result = 4;
333         const vector<Track *> &links = trk.get_links();
334         for(vector<Track *>::const_iterator i=links.begin(); i!=links.end(); ++i)
335         {
336                 if(!*i)
337                         continue;
338                 if(!tracks.count(*i))
339                         continue;
340
341                 // Linked to an existing track - good
342                 result |= 1;
343
344                 if(unsigned tid = (*i)->get_turnout_id())
345                 {
346                         const TrackType::Endpoint &ep = (*i)->get_type().get_endpoint((*i)->get_endpoint_by_link(trk));
347                         int path = get_turnout(tid);
348                         if(path>=0)
349                         {
350                                 // Linking to a turnout with path set is only good if we're continuing that path
351                                 if(ep.paths&(1<<path))
352                                         result |= 2;
353                         }
354                         else
355                         {
356                                 // Linked to a turnout with no path set - check other linked tracks 
357                                 const vector<Track *> &tlinks = (*i)->get_links();
358                                 unsigned count = 0;
359                                 for(unsigned j=0; j<tlinks.size(); ++j)
360                                         if(tracks.count(tlinks[j]))
361                                         {
362                                                 unsigned tid2 = tlinks[j]->get_turnout_id();
363                                                 if(tid2)
364                                                 {
365                                                         const TrackType::Endpoint &ep2 = tlinks[j]->get_type().get_endpoint(tlinks[j]->get_endpoint_by_link(**i));
366                                                         path = get_turnout(tid2);
367                                                         // Ignore a linked turnout with some other path set
368                                                         if(path>0 && !(ep2.paths&(1<<path)))
369                                                                 continue;
370                                                 }
371
372                                                 ++count;
373
374                                                 const TrackType::Endpoint &ep2 = (*i)->get_type().get_endpoint(j);
375                                                 if(!(ep.paths&ep2.paths))
376                                                         // Impossible path through the turnout - not good
377                                                         result &= 3;
378                                         }
379
380                                 // Only good if at most one other track is linked to the turnout
381                                 if(count<=1)
382                                         result |= 2;
383                         }
384                 }
385                 else
386                         // Linked to something linear - good
387                         result |= 2;
388         }
389
390         return result;
391 }
392
393 void Route::track_removed(Track &t)
394 {
395         tracks.erase(&t);
396 }
397
398 Route *Route::find(const TrackIter &from, Track &to)
399 {
400         return create_route(from, TrackMatch(to));
401 }
402
403 Route *Route::find(const TrackIter &from, const Route &to)
404 {
405         return create_route(from, TrackInSet(to.get_tracks()));
406 }
407
408 Route *Route::find(const TrackIter &from, const Zone &to)
409 {
410         return create_route(from, TrackInSet(to.get_tracks()));
411 }
412
413 Route *Route::find(const TrackIter &from, const set<Track *> &to)
414 {
415         return create_route(from, TrackInSet(to));
416 }
417
418
419 Route::Loader::Loader(Route &r):
420         DataFile::BasicLoader<Route>(r)
421 {
422         add("name",    &Route::name);
423         add("turnout", &Loader::turnout);
424 }
425
426 void Route::Loader::finish()
427 {
428         const set<Track *> &ltracks = obj.layout.get_tracks();
429         for(set<Track *>::const_iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
430         {
431                 unsigned tid = (*i)->get_turnout_id();
432                 if(!tid)
433                         continue;
434
435                 TurnoutMap::iterator j = turnouts.find(tid);
436                 if(j==turnouts.end())
437                         continue;
438
439                 unsigned path_mask = 1<<j->second;
440                 const vector<TrackType::Endpoint> &eps = (*i)->get_type().get_endpoints();
441                 for(unsigned k=0; k<eps.size(); ++k)
442                         if(eps[k].paths&path_mask)
443                         {
444                                 Track *link = (*i)->get_link(k);
445                                 if(!obj.tracks.count(link))
446                                         obj.add_track_chain(*link, link->get_endpoint_by_link(**i), turnouts);
447                                 if(!obj.tracks.count(*i))
448                                         obj.add_track_chain(**i, k, turnouts);
449                                 break;
450                         }
451
452                 break;
453         }
454 }
455
456 void Route::Loader::turnout(unsigned id, unsigned path)
457 {
458         turnouts[id] = path;
459 }
460
461 } // namespace R2C2