]> git.tdb.fi Git - r2c2.git/blob - source/libmarklin/route.cpp
Fix some problems with loading routes
[r2c2.git] / source / libmarklin / route.cpp
1 /* $Id$
2
3 This file is part of the MSP Märklin suite
4 Copyright © 2007-2009  Mikkosoft Productions, Mikko Rasa
5 Distributed under the GPL
6 */
7
8 #include "layout.h"
9 #include "route.h"
10 #include "track.h"
11 #include "tracktype.h"
12
13 using namespace std;
14 using namespace Msp;
15
16 namespace Marklin {
17
18 Route::Route(Layout &layout, const string &n):
19         name(n)
20 {
21         layout.signal_track_removed.connect(sigc::mem_fun(this, &Route::track_removed));
22 }
23
24 int Route::get_turnout(unsigned id) const
25 {
26         map<unsigned, int>::const_iterator i = turnouts.find(id);
27         if(i!=turnouts.end())
28                 return i->second;
29         return -1;
30 }
31
32 void Route::add_track(const Track &trk)
33 {
34         if(tracks.count(&trk))
35                 return;
36
37         if(!tracks.empty())
38         {
39                 unsigned valid = check_validity(trk);
40                 if(!(valid&1))
41                         throw Exception("Not linked to existing tracks");
42                 else if(!(valid&2))
43                         throw Exception("Branching routes not allowed");
44                 else if(!(valid&4))
45                         throw Exception("Route must be smooth");
46         }
47
48         tracks.insert(&trk);
49         update_turnouts();
50 }
51
52 void Route::add_tracks(const set<const Track *> &trks)
53 {
54         set<const Track *> pending;
55         for(set<const Track *>::const_iterator i=trks.begin(); i!=trks.end(); ++i)
56                 if(!tracks.count(*i))
57                         pending.insert(*i);
58
59         while(!pending.empty())
60         {
61                 bool found = false;
62                 for(set<const Track *>::const_iterator i=pending.begin(); i!=pending.end(); ++i)
63                         if(tracks.empty() || check_validity(**i)==7)
64                         {
65                                 tracks.insert(*i);
66                                 pending.erase(*i);
67                                 found = true;
68                                 break;
69                         }
70
71                 if(!found)
72                         throw Exception("Could not add all tracks to route");
73         }
74
75         update_turnouts();
76 }
77
78 void Route::save(list<DataFile::Statement> &st) const
79 {
80         for(map<unsigned, int>::const_iterator i=turnouts.begin(); i!=turnouts.end(); ++i)
81                 st.push_back((DataFile::Statement("turnout"), i->first, i->second));
82 }
83
84 void Route::update_turnouts()
85 {
86         set<unsigned> found;
87         for(set<const Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
88                 if(unsigned tid=(*i)->get_turnout_id())
89                 {
90                         found.insert(tid);
91
92                         const vector<Endpoint> &endpoints = (*i)->get_type().get_endpoints();
93                         const vector<Track *> &links = (*i)->get_links();
94
95                         // Build a combined path mask from linked endpoints
96                         unsigned mask = 15;
97                         for(unsigned j=0; j<endpoints.size(); ++j)
98                         {
99                                 if(!tracks.count(links[j]))
100                                         continue;
101
102                                 if(unsigned tid2=links[j]->get_turnout_id())
103                                 {
104                                         const Endpoint &ep = links[j]->get_type().get_endpoints()[links[j]->get_endpoint_by_link(**i)];
105                                         int p = get_turnout(tid2);
106                                         if(p>=0 && !(ep.paths&(1<<p)))
107                                         {
108                                                 // The linked track is a turnout and has a path which is incompatible with this endpoint
109                                                 mask &= ~endpoints[j].paths;
110                                                 continue;
111                                         }
112                                 }
113                                 mask &= endpoints[j].paths;
114                         }
115
116                         if(mask && !(mask&(mask-1)))
117                         {
118                                 // Exactly one possible choice, set the path accordingly
119                                 unsigned path = 0;
120                                 for(; (mask && !(mask&1)); mask>>=1, ++path) ;
121                                 turnouts[tid] = path;
122                         }
123                         else if(!turnouts.count(tid))
124                                 // More than one possible choice, and no existing entry - set as undecided
125                                 turnouts[tid] = -1;
126                 }
127
128         // Remove any turnouts that do not exist in the route
129         for(map<unsigned, int>::iterator i=turnouts.begin(); i!=turnouts.end();)
130         {
131                 if(!found.count(i->first))
132                         turnouts.erase(i++);
133                 else
134                         ++i;
135         }
136 }
137
138 unsigned Route::check_validity(const Track &trk) const
139 {
140         unsigned result = 4;
141         for(set<const Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
142         {
143                 int epi=(*i)->get_endpoint_by_link(trk);
144                 if(epi>=0)
145                 {
146                         // Linked to an existing track - good
147                         result |= 1;
148                         const vector<Endpoint> &endpoints = (*i)->get_type().get_endpoints();
149                         if(unsigned tid=(*i)->get_turnout_id())
150                         {
151                                 int r = get_turnout(tid);
152                                 if(r>=0)
153                                 {
154                                         // Linking to a turnout with path set is only good if we're continuing that path
155                                         if(endpoints[epi].paths&(1<<r))
156                                                 result |= 2;
157                                 }
158                                 else
159                                 {
160                                         // Linked to a turnout with no path set - find out other linked tracks 
161                                         unsigned count = 0;
162                                         const vector<Track *> &links = (*i)->get_links();
163                                         int epj = -1;
164                                         for(unsigned k=0; k<endpoints.size(); ++k)
165                                                 if(tracks.count(links[k]))
166                                                 {
167                                                         ++count;
168                                                         epj = k;
169                                                 }
170
171                                         // Only good if at most one other track is linked to the turnout
172                                         if(count<=1)
173                                         {
174                                                 result |= 2;
175                                                 if(epj>=0 && !(endpoints[epi].paths&endpoints[epj].paths))
176                                                         // Impossible path through the turnout - not good
177                                                         result &= 3;
178                                         }
179                                 }
180                         }
181                         else
182                                 // Linked to something linear - good
183                                 result |= 2;
184                 }
185         }
186
187         return result;
188 }
189
190 void Route::track_removed(Track &t)
191 {
192         tracks.erase(&t);
193 }
194
195
196 Route::Loader::Loader(Route &r):
197         DataFile::BasicLoader<Route>(r)
198 {
199         add("turnout", &Loader::turnout);
200 }
201
202 void Route::Loader::turnout(unsigned id, unsigned path)
203 {
204         obj.turnouts[id] = path;
205 }
206
207 } // namespace Marklin