]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/timetable.cpp
Sync timetable on clock discontinuity
[r2c2.git] / source / libr2c2 / timetable.cpp
1 #include <algorithm>
2 #include <msp/strings/format.h>
3 #include "aicontrol.h"
4 #include "clock.h"
5 #include "layout.h"
6 #include "timetable.h"
7 #include "train.h"
8 #include "trainrouter.h"
9 #include "zone.h"
10
11 using namespace std;
12 using namespace Msp;
13
14 namespace R2C2 {
15
16 Timetable::Timetable(Train &t):
17         TrainAI(t),
18         current_row(rows.end()),
19         update_pending(false),
20         sync_to_clock(true)
21 {
22         if(!train.get_ai_of_type<AIControl>())
23                 new AIControl(train);
24         if(!train.get_ai_of_type<TrainRouter>())
25                 new TrainRouter(train);
26
27         train.signal_ai_event.connect(sigc::mem_fun(this, &Timetable::event));
28         train.get_layout().get_clock().signal_discontinuity.connect(sigc::mem_fun(this, &Timetable::clock_discontinuity));
29 }
30
31 void Timetable::append_row(const Row &r)
32 {
33         insert_row(rows.size(), r);
34 }
35
36 void Timetable::insert_row(unsigned i, const Row &r)
37 {
38         if(i>rows.size())
39                 throw out_of_range("Timetable::insert_row");
40
41         list<Row>::iterator j = rows.begin();
42         advance(j, i);
43         j = rows.insert(j, r);
44         signal_row_added.emit(i, *j);
45
46         check_update(j);
47 }
48
49 void Timetable::modify_row(unsigned i, const Row &r)
50 {
51         if(i>=rows.size())
52                 throw out_of_range("Timetable::remove_row");
53
54         list<Row>::iterator j = rows.begin();
55         advance(j, i);
56         *j = r;
57         signal_row_modified.emit(i, r);
58
59         check_update(j);
60 }
61
62 void Timetable::remove_row(unsigned i)
63 {
64         if(i>=rows.size())
65                 throw out_of_range("Timetable::remove_row");
66
67         list<Row>::iterator j = rows.begin();
68         advance(j, i);
69
70         check_update(j);
71         if(j==current_row)
72                 --current_row;
73
74         rows.erase(j);
75         signal_row_removed.emit(i);
76 }
77
78 const Timetable::Row &Timetable::get_row(unsigned i) const
79 {
80         if(i>=rows.size())
81                 throw out_of_range("Timetable::get_row");
82
83         list<Row>::const_iterator j = rows.begin();
84         advance(j, i);
85         return *j;
86 }
87
88 void Timetable::tick(const Time::TimeDelta &dt)
89 {
90         if(update_pending && !train.get_block_allocator().is_active())
91                 update_route();
92
93         if(current_row->type==DEPART)
94         {
95                 const Clock &clock = train.get_layout().get_clock();
96
97                 Time::TimeDelta t = clock.get_current_time();
98                 if(t<current_row->time)
99                         t += Time::day;
100
101                 Time::TimeDelta b = t-dt*clock.get_rate();
102                 if(b<current_row->time)
103                 {
104                         train.ai_message(Message("set-target-speed", train.get_maximum_speed()));
105                         ++current_row;
106                 }
107         }
108 }
109
110 void Timetable::save(list<DataFile::Statement> &st) const
111 {
112         for(list<Row>::const_iterator i=rows.begin(); i!=rows.end(); ++i)
113         {
114                 DataFile::Statement ss("row");
115                 i->save(ss.sub);
116                 st.push_back(ss);
117         }
118 }
119
120 void Timetable::check_update(const list<Row>::const_iterator &i)
121 {
122         for(list<Row>::const_iterator j=current_row; (j!=rows.end() && j!=i); ++j)
123                 if(j->type==ARRIVE)
124                         return;
125         update_pending = true;
126 }
127
128 list<Timetable::Row>::iterator Timetable::find_trip(const list<Row>::iterator &begin, list<Row>::iterator *arrive)
129 {
130         list<Row>::iterator i = find_if(begin, rows.end(), RowTypeMatch(DEPART));
131         if(i==rows.end())
132                 return i;
133
134         list<Row>::iterator j = find_if(i, rows.end(), RowTypeMatch(ARRIVE));
135         if(j==rows.end())
136                 return j;
137
138         if(arrive)
139                 *arrive = j;
140         return i;
141 }
142
143 void Timetable::update_route()
144 {
145         update_pending = false;
146         if(rows.empty())
147                 return;
148
149         const Clock &clock = train.get_layout().get_clock();
150
151         if(sync_to_clock)
152         {
153                 sync_to_clock = false;
154                 current_row = rows.begin();
155                 for(list<Row>::iterator i=rows.begin(); i!=rows.end(); ++i)
156                         if(i->type==DEPART && i->time>=clock.get_current_time())
157                         {
158                                 current_row = i;
159                                 break;
160                         }
161         }
162
163         list<Row>::iterator arrive;
164         list<Row>::iterator depart = find_trip(current_row, &arrive);
165         if(depart==rows.end())
166         {
167                 depart = find_trip(rows.begin(), &arrive);
168                 if(depart==rows.end())
169                 {
170                         current_row = rows.end();
171                         return;
172                 }
173         }
174
175         train.ai_message(Message("clear-route"));
176
177         current_row = depart;
178         for(list<Row>::const_iterator i=depart; i!=rows.end(); ++i)
179         {
180                 if(i->type==DEPART)
181                 {
182                         Time::TimeDelta dt = i->time-clock.get_current_time();
183                         while(dt<Time::zero)
184                                 dt += Time::day;
185                         train.ai_message(Message("set-departure-delay", dt/clock.get_rate()));
186                 }
187                 else
188                 {
189                         train.ai_message(Message("add-waypoint", TrainRouter::Waypoint(*i->target, i->direction)));
190                         if(i->type==ARRIVE)
191                                 break;
192                 }
193         }
194
195         list<Row>::iterator next_depart = find_trip(arrive, 0);
196         if(next_depart==rows.end())
197                 next_depart = find_trip(rows.begin(), 0);
198         if(next_depart!=rows.end())
199         {
200                 Time::TimeDelta dt = next_depart->time-depart->time;
201                 while(dt<=Time::zero)
202                         dt += Time::day;
203                 train.ai_message(Message("set-trip-duration", dt/clock.get_rate()));
204         }
205 }
206
207 void Timetable::event(TrainAI &, const Message &msg)
208 {
209         if(msg.type=="arrived")
210         {
211                 if(current_row->type==ARRIVE)
212                         record_time();
213                 update_pending = true;
214         }
215         else if(msg.type=="waypoint-reached")
216         {
217                 const TrackChain *wp = msg.value.value<const TrackChain *>();
218                 if(current_row->type==THROUGH && current_row->target==wp)
219                 {
220                         record_time();
221                         ++current_row;
222                 }
223         }
224 }
225
226 void Timetable::record_time()
227 {
228         current_row->time = train.get_layout().get_clock().get_current_time();
229         unsigned i = distance(rows.begin(), current_row);
230         signal_row_modified.emit(i, *current_row);
231 }
232
233 void Timetable::clock_discontinuity()
234 {
235         update_pending = true;
236         sync_to_clock = true;
237 }
238
239
240 Timetable::Row::Row():
241         type(ARRIVE),
242         target(0),
243         direction(TrackChain::UNSPECIFIED)
244 { }
245
246 void Timetable::Row::save(list<DataFile::Statement> &st) const
247 {
248         st.push_back((DataFile::Statement("type"), type));
249         st.push_back((DataFile::Statement("time"), time.raw()));
250         st.push_back(target->save_reference());
251         if(direction)
252                 st.push_back((DataFile::Statement("direction"), direction));
253 }
254
255
256 Timetable::Loader::Loader(Timetable &t, Layout &l):
257         DataFile::ObjectLoader<Timetable>(t),
258         layout(l)
259 {
260         add("row", &Loader::row);
261 }
262
263 void Timetable::Loader::row()
264 {
265         Row r;
266         load_sub(r, layout);
267         obj.rows.push_back(r);
268         obj.update_pending = true;
269 }
270
271
272 Timetable::Row::Loader::Loader(Row &r, Layout &l):
273         DataFile::ObjectLoader<Timetable::Row>(r),
274         layout(l)
275 {
276         add("block", &Loader::block);
277         add("direction", &Row::direction);
278         add("time", &Loader::time);
279         add("type", &Row::type);
280         add("zone", &Loader::zone);
281         add("zone", &Loader::zone_numbered);
282 }
283
284 void Timetable::Row::Loader::block(unsigned id)
285 {
286         obj.target = &layout.get_block(id);
287 }
288
289 void Timetable::Row::Loader::time(Time::RawTime t)
290 {
291         obj.time = Time::TimeDelta(t);
292 }
293
294 void Timetable::Row::Loader::zone(const string &name)
295 {
296         zone_numbered(name, 0);
297 }
298
299 void Timetable::Row::Loader::zone_numbered(const string &name, unsigned number)
300 {
301         obj.target = &layout.get_zone(name, number);
302 }
303
304
305 void operator<<(LexicalConverter &conv, Timetable::RowType rt)
306 {
307         switch(rt)
308         {
309         case Timetable::ARRIVE: conv.result("ARRIVE"); return;
310         case Timetable::DEPART: conv.result("DEPART"); return;
311         case Timetable::THROUGH: conv.result("THROUGH"); return;
312         default: throw lexical_error(format("conversion of RowType(%d) to string", rt));
313         }
314 }
315
316 void operator>>(const LexicalConverter &conv, Timetable::RowType &rt)
317 {
318         if(conv.get()=="ARRIVE")
319                 rt = Timetable::ARRIVE;
320         else if(conv.get()=="DEPART")
321                 rt = Timetable::DEPART;
322         else if(conv.get()=="THROUGH")
323                 rt = Timetable::THROUGH;
324         else
325                 throw lexical_error(format("conversion of '%s' to RowType", conv.get()));
326 }
327
328 } // namespace R2C2