]> git.tdb.fi Git - r2c2.git/blobdiff - source/libr2c2/timetable.cpp
Allow zones with no qualifier or no number
[r2c2.git] / source / libr2c2 / timetable.cpp
index 8b469b52fd848feb52f09c07346a521780c1d8ca..5fa36395825d0afdb6dfb92df83e76f97d3cd6cb 100644 (file)
@@ -1,18 +1,12 @@
-/* $Id$
-
-This file is part of R²C²
-Copyright © 2010  Mikkosoft Productions, Mikko Rasa
-Distributed under the GPL
-*/
-
-#include <msp/strings/formatter.h>
-#include <msp/time/units.h>
-#include "block.h"
-#include "catalogue.h"
-#include "driver.h"
+#include <algorithm>
+#include <msp/strings/format.h>
+#include "aicontrol.h"
+#include "clock.h"
 #include "layout.h"
 #include "timetable.h"
 #include "train.h"
+#include "trainrouter.h"
+#include "zone.h"
 
 using namespace std;
 using namespace Msp;
@@ -20,404 +14,308 @@ using namespace Msp;
 namespace R2C2 {
 
 Timetable::Timetable(Train &t):
-       train(t),
-       enabled(false),
-       current_row(0),
-       executing(true),
-       pending_block(0),
-       pending_train(0),
-       arrived(false)
+       TrainAI(t),
+       current_row(rows.end()),
+       update_pending(false),
+       sync_to_clock(true)
 {
-       train.signal_arrived.connect(sigc::mem_fun(this, &Timetable::train_arrived));
-       train.get_layout().get_driver().signal_sensor.connect(sigc::mem_fun(this, &Timetable::sensor_event));
-}
+       if(!train.get_ai_of_type<AIControl>())
+               new AIControl(train);
+       if(!train.get_ai_of_type<TrainRouter>())
+               new TrainRouter(train);
 
-void Timetable::set_enabled(bool e)
-{
-       enabled = e;
+       train.signal_ai_event.connect(sigc::mem_fun(this, &Timetable::event));
 }
 
-void Timetable::reset()
+void Timetable::append_row(const Row &r)
 {
-       current_row = 0;
-       wait_timeout = Time::TimeStamp();
-       pending_block = 0;
-       executing = true;
+       insert_row(rows.size(), r);
 }
 
-void Timetable::clear()
+void Timetable::insert_row(unsigned i, const Row &r)
 {
-       rows.clear();
-       reset();
+       if(i>rows.size())
+               throw out_of_range("Timetable::insert_row");
+
+       list<Row>::iterator j = rows.begin();
+       advance(j, i);
+       j = rows.insert(j, r);
+       signal_row_added.emit(i, *j);
+
+       check_update(j);
 }
 
-void Timetable::append(const Row &row)
+void Timetable::modify_row(unsigned i, const Row &r)
 {
-       rows.push_back(row);
+       if(i>=rows.size())
+               throw out_of_range("Timetable::remove_row");
+
+       list<Row>::iterator j = rows.begin();
+       advance(j, i);
+       *j = r;
+       signal_row_modified.emit(i, r);
+
+       check_update(j);
 }
 
-void Timetable::insert(unsigned i, const Row &row)
+void Timetable::remove_row(unsigned i)
 {
-       if(i>rows.size())
-               throw InvalidParameterValue("Insert position out of range");
+       if(i>=rows.size())
+               throw out_of_range("Timetable::remove_row");
+
+       list<Row>::iterator j = rows.begin();
+       advance(j, i);
+
+       check_update(j);
+       if(j==current_row)
+               --current_row;
 
-       rows.insert(rows.begin()+i, row);
-       if(i<=current_row)
-               ++current_row;
+       rows.erase(j);
+       signal_row_removed.emit(i);
 }
 
 const Timetable::Row &Timetable::get_row(unsigned i) const
 {
        if(i>=rows.size())
-               throw InvalidParameterValue("Row index out of range");
-       return rows[i];
+               throw out_of_range("Timetable::get_row");
+
+       list<Row>::const_iterator j = rows.begin();
+       advance(j, i);
+       return *j;
 }
 
-void Timetable::tick(const Time::TimeStamp &t)
+void Timetable::tick(const Time::TimeDelta &dt)
 {
-       if(rows.empty() || !enabled)
-               return;
+       if(update_pending && !train.get_block_allocator().is_active())
+               update_route();
 
-       if(wait_timeout && t>=wait_timeout)
+       if(current_row->type==DEPART)
        {
-               wait_timeout = Time::TimeStamp();
-               current_row = (current_row+1)%rows.size();
-               executing = true;
-       }
+               const Clock &clock = train.get_layout().get_clock();
 
-       if(executing)
-       {
-               Row &row = rows[current_row];
-               switch(row.type)
+               Time::TimeDelta t = clock.get_current_time();
+               if(t<current_row->time)
+                       t += Time::day;
+
+               Time::TimeDelta b = t-dt*clock.get_rate();
+               if(b<current_row->time)
                {
-               case GOTO_SENSOR:
-                       arrived = false;
-                       if(!train.go_to(get_sensor(row.get_param<unsigned>(0))))
-                               set_enabled(false);
-                       break;
-               case GOTO_ZONE:
-                       arrived = false;
-                       if(!train.go_to(get_zone(row.get_param<string>(0))))
-                               set_enabled(false);
-                       break;
-               case TRAVEL:
-                       pending_block = &get_sensor(row.get_param<unsigned>(0)).get_block();
-                       pending_train = &train;
-                       executing = false;
-                       break;
-               case WAIT_TIME:
-                       wait_timeout = t+row.get_param<unsigned>(0)*Time::sec;
-                       executing = false;
-                       break;
-               case WAIT_UNTIL:
-                       {
-                               unsigned unixtime = t.to_unixtime();
-                               unsigned mod = row.get_param<unsigned>(1);
-                               unsigned secs = ((mod+row.get_param<unsigned>(0))-(unixtime%mod))%mod;
-                               wait_timeout = t+secs*Time::sec;
-                               executing = false;
-                       }
-                       break;
-               case WAIT_TRAIN:
-                       pending_train = &train.get_layout().get_train(row.get_param<unsigned>(0));
-                       pending_block = &get_sensor(row.get_param<unsigned>(1)).get_block();
-                       executing = false;
-                       break;
-               case ARRIVE:
-                       if(!arrived)
-                               executing = false;
-                       arrived = false;
-                       break;
-               case SPEED:
-                       if(!arrived)
-                               train.set_control("speed", row.get_param<unsigned>(0)/3.6*train.get_layout().get_catalogue().get_scale());
-                       break;
-               case REVERSE:
-                       train.set_control("reverse", !train.get_control("reverse"));
-                       break;
-               case ROUTE:
-                       if(!train.set_route(&train.get_layout().get_route(row.get_param<string>(0))))
-                               set_enabled(false);
-                       break;
+                       train.ai_message(Message("set-target-speed", train.get_maximum_speed()));
+                       ++current_row;
                }
-
-               if(executing)
-                       current_row = (current_row+1)%rows.size();
        }
 }
 
 void Timetable::save(list<DataFile::Statement> &st) const
 {
-       for(vector<Row>::const_iterator i=rows.begin(); i!=rows.end(); ++i)
-               st.push_back(i->save());
-}
-
-Track &Timetable::get_sensor(unsigned id)
-{
-       Block &block = train.get_layout().get_block(id|0x1000);
-       return **block.get_tracks().begin();
-}
-
-Zone &Timetable::get_zone(const string &name)
-{
-       string::size_type space = name.rfind(' ');
-       if(space==string::npos || space==0)
-               throw InvalidParameterValue("Invalid zone name");
-       unsigned number = lexical_cast<unsigned>(name.substr(space+1));
-       return train.get_layout().get_zone(name.substr(0, space), number);
-}
-
-void Timetable::sensor_event(unsigned addr, bool state)
-{
-       if(pending_block && pending_block->get_train()==pending_train && addr==pending_block->get_sensor_id() && state)
+       for(list<Row>::const_iterator i=rows.begin(); i!=rows.end(); ++i)
        {
-               pending_block = 0;
-               current_row = (current_row+1)%rows.size();
-               executing = true;
+               DataFile::Statement ss("row");
+               i->save(ss.sub);
+               st.push_back(ss);
        }
 }
 
-void Timetable::train_arrived()
+void Timetable::check_update(const list<Row>::const_iterator &i)
 {
-       Row &row = rows[current_row];
-       if(row.type==ARRIVE)
-       {
-               current_row = (current_row+1)%rows.size();
-               executing = true;
-       }
-       else
-               arrived = true;
+       for(list<Row>::const_iterator j=current_row; (j!=rows.end() && j!=i); ++j)
+               if(j->type==ARRIVE)
+                       return;
+       update_pending = true;
 }
 
+list<Timetable::Row>::iterator Timetable::find_trip(const list<Row>::iterator &begin, list<Row>::iterator *arrive)
+{
+       list<Row>::iterator i = find_if(begin, rows.end(), RowTypeMatch(DEPART));
+       if(i==rows.end())
+               return i;
 
-Timetable::Row::Row(RowType t):
-       type(t)
-{ }
+       list<Row>::iterator j = find_if(i, rows.end(), RowTypeMatch(ARRIVE));
+       if(j==rows.end())
+               return j;
 
-template<typename T>
-Timetable::Row::Row(RowType t, const T &p):
-       type(t)
-{
-       params.push_back(p);
+       if(arrive)
+               *arrive = j;
+       return i;
 }
 
-template<typename T>
-const T &Timetable::Row::get_param(unsigned i) const
+void Timetable::update_route()
 {
-       if(i>=params.size())
-               throw InvalidParameterValue("Parameter index out of range");
-       return params[i].value<T>();
-}
+       update_pending = false;
+       if(rows.empty())
+               return;
 
-string Timetable::Row::str() const
-{
-       switch(type)
-       {
-       case GOTO_SENSOR:
-               return "set route to sensor "+get_param<unsigned>(0);
-       case GOTO_ZONE:
-               return "set route to "+get_param<string>(0);
-       case TRAVEL:
-               return format("travel to sensor %d", get_param<unsigned>(0));
-       case WAIT_TIME:
-               return format("wait for %d seconds", get_param<unsigned>(0));
-       case WAIT_UNTIL:
-               return format("wait until %d mod %d seconds", get_param<unsigned>(0), get_param<unsigned>(1));
-       case WAIT_TRAIN:
-               return format("wait for train %d at %s", get_param<unsigned>(0), get_param<string>(1));
-       case ARRIVE:
-               return "travel until arrival";
-       case SPEED:
-               return format("set speed %d km/h", get_param<unsigned>(0));
-       case REVERSE:
-               return "reverse";
-       case ROUTE:
-               return "set route "+get_param<string>(0);
-       default:
-               return "invalid row";
-       }
-}
+       const Clock &clock = train.get_layout().get_clock();
 
-DataFile::Statement Timetable::Row::save() const
-{
-       switch(type)
+       if(sync_to_clock)
        {
-       case GOTO_SENSOR:
-               return DataFile::Statement("goto_sensor"), get_param<unsigned>(0);
-       case GOTO_ZONE:
-               return DataFile::Statement("goto_zone"), get_param<string>(0);
-       case TRAVEL:
-               return DataFile::Statement("travel"), get_param<unsigned>(0);
-       case WAIT_TIME:
-               return DataFile::Statement("wait"), get_param<unsigned>(0);
-       case WAIT_UNTIL:
-               return DataFile::Statement("wait_until"), get_param<unsigned>(0), get_param<unsigned>(1);
-       case WAIT_TRAIN:
-               return DataFile::Statement("wait_train"), get_param<unsigned>(0), get_param<unsigned>(1);
-       case ARRIVE:
-               return DataFile::Statement("arrive");
-       case SPEED:
-               return DataFile::Statement("speed"), get_param<unsigned>(0);
-       case REVERSE:
-               return DataFile::Statement("reverse");
-       case ROUTE:
-               return DataFile::Statement("route"), get_param<string>(0);
-       default:
-               return DataFile::Statement();
+               sync_to_clock = false;
+               current_row = rows.begin();
+               for(list<Row>::iterator i=rows.begin(); i!=rows.end(); ++i)
+                       if(i->type==DEPART && i->time>=clock.get_current_time())
+                       {
+                               current_row = i;
+                               break;
+                       }
        }
-}
 
-Timetable::Row Timetable::Row::parse(const string &s)
-{
-       if(!s.compare(0, 7, "travel "))
-       {
-               if(!s.compare(7, 10, "to sensor "))
-                       return Row(TRAVEL, lexical_cast<unsigned>(s.substr(17)));
-               else if(!s.compare(7, string::npos, "until arrival"))
-                       return Row(ARRIVE);
-       }
-       else if(!s.compare(0, 9, "wait for "))
+       list<Row>::iterator arrive;
+       list<Row>::iterator depart = find_trip(current_row, &arrive);
+       if(depart==rows.end())
        {
-               if(isdigit(s[9]))
+               depart = find_trip(rows.begin(), &arrive);
+               if(depart==rows.end())
                {
-                       unsigned nondigit = 10;
-                       while(nondigit<s.size() && isdigit(s[nondigit]))
-                               ++nondigit;
-                       return Row(WAIT_TIME, lexical_cast<unsigned>(s.substr(9, nondigit-9)));
-               }
-               else if(!s.compare(9, 6, "train "))
-               {
-                       string::size_type at = s.find(" at sensor ", 15);
-                       if(at!=string::npos)
-                       {
-                               Row row(WAIT_TRAIN, lexical_cast<unsigned>(s.substr(15, at-15)));
-                               row.params.push_back(lexical_cast<unsigned>(s.substr(at+11)));
-                               return row;
-                       }
+                       current_row = rows.end();
+                       return;
                }
        }
-       else if(!s.compare(0, 11, "wait until "))
+
+       train.ai_message(Message("clear-route"));
+
+       current_row = depart;
+       for(list<Row>::const_iterator i=depart; i!=rows.end(); ++i)
        {
-               string::size_type mod = s.find(" mod ", 11);
-               unsigned nondigit = (mod!=string::npos ? mod+5 : 11);
-               while(nondigit<s.size() && isdigit(s[nondigit]))
-                       ++nondigit;
-               if(mod!=string::npos)
+               if(i->type==DEPART)
                {
-                       unsigned time = lexical_cast<unsigned>(s.substr(11, mod-11));
-                       Row row(WAIT_UNTIL, time);
-                       row.params.push_back(lexical_cast<unsigned>(s.substr(mod+5, nondigit-mod-5)));
-                       return row;
+                       Time::TimeDelta dt = i->time-clock.get_current_time();
+                       while(dt<Time::zero)
+                               dt += Time::day;
+                       train.ai_message(Message("set-departure-delay", dt/clock.get_rate()));
                }
                else
                {
-                       unsigned time = lexical_cast<unsigned>(s.substr(11, nondigit-11));
-                       Row row(WAIT_UNTIL, time);
-                       row.params.push_back(3600);
-                       return row;
+                       train.ai_message(Message("add-waypoint", TrainRouter::Waypoint(*i->target, i->direction)));
+                       if(i->type==ARRIVE)
+                               break;
                }
        }
-       else if(!s.compare(0, 10, "set speed "))
+
+       list<Row>::iterator next_depart = find_trip(arrive, 0);
+       if(next_depart==rows.end())
+               next_depart = find_trip(rows.begin(), 0);
+       if(next_depart!=rows.end())
        {
-               unsigned nondigit = 11;
-               while(nondigit<s.size() && (isdigit(s[nondigit]) || s[nondigit]=='-'))
-                       ++nondigit;
-               return Row(SPEED, lexical_cast<unsigned>(s.substr(10, nondigit-10)));
+               Time::TimeDelta dt = next_depart->time-depart->time;
+               while(dt<=Time::zero)
+                       dt += Time::day;
+               train.ai_message(Message("set-trip-duration", dt/clock.get_rate()));
        }
-       else if(s=="reverse")
-               return Row(REVERSE);
-       else if(!s.compare(0, 10, "set route "))
+}
+
+void Timetable::event(TrainAI &, const Message &msg)
+{
+       if(msg.type=="arrived")
        {
-               if(!s.compare(10, 3, "to "))
+               if(current_row->type==ARRIVE)
+                       record_time();
+               update_pending = true;
+       }
+       else if(msg.type=="waypoint-reached")
+       {
+               const TrackChain *wp = msg.value.value<const TrackChain *>();
+               if(current_row->type==THROUGH && current_row->target==wp)
                {
-                       if(!s.compare(13, 7, "sensor "))
-                               return Row(GOTO_SENSOR, lexical_cast<unsigned>(s.substr(20)));
-                       else
-                               return Row(GOTO_ZONE, s.substr(13));
+                       record_time();
+                       ++current_row;
                }
-               return Row(ROUTE, s.substr(10));
        }
-
-       throw InvalidParameterValue("Invalid row");
 }
 
-
-Timetable::Loader::Loader(Timetable &tt):
-       DataFile::ObjectLoader<Timetable>(tt)
+void Timetable::record_time()
 {
-       add("arrive",      &Loader::arrive);
-       add("goto_sensor", static_cast<void (Loader::*)(unsigned)>(&Loader::goto_sensor));
-       add("goto_zone",   &Loader::goto_zone);
-       add("route",       &Loader::route);
-       add("speed",       &Loader::speed);
-       add("reverse",     &Loader::reverse);
-       add("travel",      &Loader::travel);
-       add("wait",        &Loader::wait);
-       add("wait_train",  &Loader::wait_train);
-       add("wait_until",  &Loader::wait_until);
-
-       // Deprecated alias
-       add("goto",        static_cast<void (Loader::*)(const string &)>(&Loader::goto_sensor));
+       current_row->time = train.get_layout().get_clock().get_current_time();
+       unsigned i = distance(rows.begin(), current_row);
+       signal_row_modified.emit(i, *current_row);
 }
 
-void Timetable::Loader::arrive()
-{
-       obj.rows.push_back(Row(ARRIVE));
-}
 
-void Timetable::Loader::goto_sensor(unsigned s)
+Timetable::Row::Row():
+       type(ARRIVE),
+       target(0),
+       direction(TrackChain::UNSPECIFIED)
+{ }
+
+void Timetable::Row::save(list<DataFile::Statement> &st) const
 {
-       obj.rows.push_back(Row(GOTO_SENSOR, s));
+       st.push_back((DataFile::Statement("type"), type));
+       st.push_back((DataFile::Statement("time"), time.raw()));
+       st.push_back(target->save_reference());
+       if(direction)
+               st.push_back((DataFile::Statement("direction"), direction));
 }
 
-void Timetable::Loader::goto_sensor(const string &s)
+
+Timetable::Loader::Loader(Timetable &t, Layout &l):
+       DataFile::ObjectLoader<Timetable>(t),
+       layout(l)
 {
-       if(!s.compare(0, 7, "sensor "))
-               obj.rows.push_back(Row(GOTO_SENSOR, lexical_cast<unsigned>(s.substr(7))));
+       add("row", &Loader::row);
 }
 
-void Timetable::Loader::goto_zone(const string &z)
+void Timetable::Loader::row()
 {
-       obj.rows.push_back(Row(GOTO_ZONE, z));
+       Row r;
+       load_sub(r, layout);
+       obj.rows.push_back(r);
+       obj.update_pending = true;
 }
 
-void Timetable::Loader::route(const string &r)
+
+Timetable::Row::Loader::Loader(Row &r, Layout &l):
+       DataFile::ObjectLoader<Timetable::Row>(r),
+       layout(l)
 {
-       obj.rows.push_back(Row(ROUTE, r));
+       add("block", &Loader::block);
+       add("direction", &Row::direction);
+       add("time", &Loader::time);
+       add("type", &Row::type);
+       add("zone", &Loader::zone);
+       add("zone", &Loader::zone_numbered);
 }
 
-void Timetable::Loader::reverse()
+void Timetable::Row::Loader::block(unsigned id)
 {
-       obj.rows.push_back(Row(REVERSE));
+       obj.target = &layout.get_block(id);
 }
 
-void Timetable::Loader::speed(unsigned s)
+void Timetable::Row::Loader::time(Time::RawTime t)
 {
-       obj.rows.push_back(Row(SPEED, s));
+       obj.time = Time::TimeDelta(t);
 }
 
-void Timetable::Loader::travel(unsigned s)
+void Timetable::Row::Loader::zone(const string &name)
 {
-       obj.rows.push_back(Row(TRAVEL, s));
+       zone_numbered(name, 0);
 }
 
-void Timetable::Loader::wait(unsigned t)
+void Timetable::Row::Loader::zone_numbered(const string &name, unsigned number)
 {
-       obj.rows.push_back(Row(WAIT_TIME, t));
+       obj.target = &layout.get_zone(name, number);
 }
 
-void Timetable::Loader::wait_train(unsigned t, unsigned s)
+
+void operator<<(LexicalConverter &conv, Timetable::RowType rt)
 {
-       Row row(WAIT_TRAIN, t);
-       row.params.push_back(s);
-       obj.rows.push_back(row);
+       switch(rt)
+       {
+       case Timetable::ARRIVE: conv.result("ARRIVE"); return;
+       case Timetable::DEPART: conv.result("DEPART"); return;
+       case Timetable::THROUGH: conv.result("THROUGH"); return;
+       default: throw lexical_error(format("conversion of RowType(%d) to string", rt));
+       }
 }
 
-void Timetable::Loader::wait_until(unsigned t, unsigned m)
+void operator>>(const LexicalConverter &conv, Timetable::RowType &rt)
 {
-       Row row(WAIT_UNTIL, t);
-       row.params.push_back(m);
-       obj.rows.push_back(row);
+       if(conv.get()=="ARRIVE")
+               rt = Timetable::ARRIVE;
+       else if(conv.get()=="DEPART")
+               rt = Timetable::DEPART;
+       else if(conv.get()=="THROUGH")
+               rt = Timetable::THROUGH;
+       else
+               throw lexical_error(format("conversion of '%s' to RowType", conv.get()));
 }
 
 } // namespace R2C2