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