]> git.tdb.fi Git - r2c2.git/commitdiff
TrainAI framework
authorMikko Rasa <tdb@tdb.fi>
Tue, 5 Apr 2011 15:49:19 +0000 (15:49 +0000)
committerMikko Rasa <tdb@tdb.fi>
Tue, 5 Apr 2011 15:49:19 +0000 (15:49 +0000)
Make AIControl an AI instead of a Controller
Remote control is a bit broken right now

source/engineer/trainpanel.cpp
source/engineer/trainpanel.h
source/libr2c2/aicontrol.cpp
source/libr2c2/aicontrol.h
source/libr2c2/timetable.cpp
source/libr2c2/timetable.h
source/libr2c2/train.cpp
source/libr2c2/train.h
source/libr2c2/trainai.cpp [new file with mode: 0644]
source/libr2c2/trainai.h [new file with mode: 0644]

index 5d237ecbca3b912b5297ea01748fcdb190891e71..813a53417452b43807650df9035e1b2ad34227b3 100644 (file)
@@ -1,12 +1,13 @@
 /* $Id$
 
 This file is part of R²C²
-Copyright © 2006-2010  Mikkosoft Productions, Mikko Rasa
+Copyright © 2006-2011  Mikkosoft Productions, Mikko Rasa
 Distributed under the GPL
 */
 
 #include <cmath>
 #include <msp/strings/formatter.h>
+#include "libr2c2/aicontrol.h"
 #include "libr2c2/timetable.h"
 #include "libr2c2/trackiter.h"
 #include "libr2c2/vehicletype.h"
@@ -28,7 +29,8 @@ TrainPanel::TrainPanel(Engineer &e, Train &t):
 {
        set_size(200, 65);
 
-       train.signal_control_changed.connect(sigc::mem_fun(this, &TrainPanel::train_control_changed));
+       AIControl *ai = new AIControl(train);
+       ai->signal_event.connect(sigc::mem_fun(this, &TrainPanel::ai_event));
 
        add(*(pnl_basic = new GLtk::Panel));
        pnl_basic->set_style("group");
@@ -146,16 +148,16 @@ void TrainPanel::expand(bool e)
        engineer.rearrange_panels();
 }
 
-void TrainPanel::train_control_changed(const string &control, float value)
+void TrainPanel::ai_event(const TrainAI::Message &msg)
 {
-       if(control=="speed")
+       if(msg.type=="target-speed-changed")
        {
-               float speed = value/engineer.get_layout().get_catalogue().get_scale()*3.6;
+               float speed = msg.value.value<float>()/engineer.get_layout().get_catalogue().get_scale()*3.6;
                sld_speed->set_value(speed);
                lbl_speed->set_text(format("%3.0f", speed));
        }
-       else if(control=="reverse")
-               tgl_forward->set_value(value==0);
+       else if(msg.type=="reverse-changed")
+               tgl_forward->set_value(msg.value.value<bool>());
 }
 
 void TrainPanel::train_function_changed(unsigned func, bool value)
@@ -211,11 +213,11 @@ void TrainPanel::goto_clicked()
 
 void TrainPanel::timetable_clicked()
 {
-       Timetable *timetable = train.get_timetable();
+       Timetable *timetable = dynamic_cast<Timetable *>(train.get_tagged_ai("timetable"));
        if(!timetable)
        {
                timetable = new Timetable(train);
-               train.set_timetable(timetable);
+               timetable->set_tag("timetable");
        }
 
        TimetableDialog *dialog = new TimetableDialog(*timetable);
@@ -238,18 +240,12 @@ void TrainPanel::expand_clicked()
 void TrainPanel::speed_slider_changed(double value)
 {
        float speed = value/3.6*engineer.get_layout().get_catalogue().get_scale();
-       train.set_control("speed", speed);
+       train.ai_message(TrainAI::Message("set-target-speed", speed));
 }
 
 void TrainPanel::forward_toggled(bool value)
 {
-       if(train.get_speed() || sld_speed->get_value())
-       {
-               train.set_control("speed", 0);
-               tgl_forward->set_value(!train.get_control("reverse"));
-       }
-       else
-               train.set_control("reverse", !value);
+       train.ai_message(TrainAI::Message("set-reverse", !value));
 }
 
 void TrainPanel::func_toggled(bool value, unsigned func)
index d74ccbf88695e40c960a93c72975f9de31d33b97..87803ec348bf94cc4f3d7ea6a4c3615a5c1a67c5 100644 (file)
@@ -1,7 +1,7 @@
 /* $Id$
 
 This file is part of R²C²
-Copyright © 2006-2010  Mikkosoft Productions, Mikko Rasa
+Copyright © 2006-2011  Mikkosoft Productions, Mikko Rasa
 Distributed under the GPL
 */
 
@@ -43,7 +43,7 @@ public:
        void expand(bool = true);
 
 private:
-       void train_control_changed(const std::string &, float);
+       void ai_event(const R2C2::TrainAI::Message &);
        void train_function_changed(unsigned, bool);
        void train_route_changed(const R2C2::Route *);
        void train_status_changed(const std::string &);
index e852858552b8120de5670d5358086d30799b6758..e5bed9ce3ba3ee71a61676f196ef673693ec667a 100644 (file)
@@ -15,89 +15,55 @@ using namespace Msp;
 
 namespace R2C2 {
 
-AIControl::AIControl(Train &t, Controller *n):
-       train(t),
-       next_ctrl(n),
-       target_speed(Control::continuous("speed", 0, 1000)),
-       state(NORMAL)
+AIControl::AIControl(Train &t):
+       TrainAI(t),
+       target_speed(0),
+       reverse(false),
+       pending_reverse(false),
+       state(NORMAL),
+       need_update(false)
 {
-       target_speed.set(0);
-
        train.signal_arrived.connect(sigc::mem_fun(this, &AIControl::arrived));
-       next_ctrl->signal_control_changed.connect(sigc::mem_fun(this, &AIControl::control_changed));
 }
 
-AIControl::~AIControl()
+void AIControl::set_target_speed(float s)
 {
-       delete next_ctrl;
-}
+       if(s && !train.is_active())
+               train.set_active(true);
 
-const char *AIControl::enumerate_controls(unsigned index) const
-{
-       if(index==0)
-               return target_speed.name.c_str();
-       else
-       {
-               for(--index;; ++index)
-               {
-                       const char *ret = next_ctrl->enumerate_controls(index-1);
-                       if(!ret || ret!=target_speed.name)
-                               return ret;
-               }
-       }
+       target_speed = s;
+       need_update = true;
+       signal_event.emit(Message("target-speed-changed", target_speed));
 }
 
-void AIControl::set_control(const string &n, float v)
+void AIControl::set_reverse(bool r)
 {
-       if(n==target_speed.name)
+       pending_reverse = r;
+       if(train.get_controller().get_speed())
+               set_target_speed(0);
+       else
        {
-               if(v && !train.is_active())
-                       train.set_active(true);
-
-               target_speed.set(v);
-               if(state!=BLOCKED)
-               {
-                       float approach_speed = 5*train.get_layout().get_catalogue().get_scale();
-                       if(state==APPROACH && target_speed.value>approach_speed)
-                               next_ctrl->set_control("speed", approach_speed);
-                       else
-                               next_ctrl->set_control("speed", target_speed.value);
-               }
-
-               signal_control_changed.emit(target_speed);
+               reverse = r;
+               train.set_control("reverse", reverse);
+               signal_event.emit(Message("reverse-changed", reverse));
        }
-       else
-               next_ctrl->set_control(n, v);
-}
-
-const Controller::Control &AIControl::get_control(const string &n) const
-{
-       if(n==target_speed.name)
-               return target_speed;
-       else
-               return next_ctrl->get_control(n);
 }
 
-float AIControl::get_speed() const
+void AIControl::message(const Message &msg)
 {
-       return next_ctrl->get_speed();
+       if(msg.type=="set-target-speed")
+               set_target_speed(msg.value.value<float>());
+       else if(msg.type=="set-reverse")
+               set_reverse(msg.value.value<bool>());
+       else if(msg.type=="toggle-reverse")
+               set_reverse(!reverse);
 }
 
-bool AIControl::get_reverse() const
-{
-       return next_ctrl->get_reverse();
-}
-
-float AIControl::get_braking_distance() const
-{
-       return next_ctrl->get_braking_distance();
-}
-
-void AIControl::tick(const Time::TimeDelta &dt)
+void AIControl::tick(const Time::TimeStamp &, const Time::TimeDelta &)
 {
        float scale = train.get_layout().get_catalogue().get_scale();
        float rsv_dist = train.get_reserved_distance();
-       float brake_dist = next_ctrl->get_braking_distance();
+       float brake_dist = train.get_controller().get_braking_distance();
        float approach_margin = 50*scale;
        float approach_speed = 5*scale;
        float margin = 1*scale;
@@ -117,7 +83,7 @@ void AIControl::tick(const Time::TimeDelta &dt)
        if(state==NORMAL && train.get_preceding_train())
                state = FOLLOW;
 
-       if(state!=old_state || state==FOLLOW)
+       if(state!=old_state || state==FOLLOW || need_update)
        {
                float speed_limit = -1;
                if(state==BLOCKED)
@@ -127,27 +93,27 @@ void AIControl::tick(const Time::TimeDelta &dt)
                else if(state==FOLLOW && train.get_preceding_train()->is_active())
                        speed_limit = train.get_preceding_train()->get_speed();
 
-               if(speed_limit>=0 && target_speed.value>speed_limit)
-                       next_ctrl->set_control("speed", speed_limit);
+               if(speed_limit>=0 && target_speed>speed_limit)
+                       train.set_control("speed", speed_limit);
                else
-                       next_ctrl->set_control("speed", target_speed.value);
+                       train.set_control("speed", target_speed);
+
+               need_update = false;
        }
 
-       next_ctrl->tick(dt);
+       if(pending_reverse!=reverse && !train.get_controller().get_speed())
+       {
+               reverse = pending_reverse;
+               train.set_control("reverse", reverse);
+       }
 
-       if(!target_speed.value && !next_ctrl->get_speed() && train.is_active())
+       if(!target_speed && !train.get_controller().get_speed() && train.is_active())
                train.set_active(false);
 }
 
-void AIControl::control_changed(const Control &ctrl)
-{
-       if(ctrl.name!="speed")
-               signal_control_changed.emit(ctrl);
-}
-
 void AIControl::arrived()
 {
-       set_control("speed", 0);
+       set_target_speed(0);
 }
 
 } // namespace R2C2
index 8d9ff11591eba395c414b6197d99652454eb06d6..01429cdd01581481918eefe10e1d22abd6d90c0e 100644 (file)
@@ -1,7 +1,7 @@
 /* $Id$
 
 This file is part of R²C²
-Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Copyright © 2010-2011  Mikkosoft Productions, Mikko Rasa
 Distributed under the GPL
 */
 
@@ -9,13 +9,13 @@ Distributed under the GPL
 #define LIBR2C2_AICONTROL_H_
 
 #include <sigc++/trackable.h>
-#include "controller.h"
+#include "trainai.h"
 
 namespace R2C2 {
 
 class Train;
 
-class AIControl: public Controller, public sigc::trackable
+class AIControl: public TrainAI, public sigc::trackable
 {
 private:
        enum State
@@ -26,27 +26,24 @@ private:
                FOLLOW
        };
 
-       Train &train;
-       Controller *next_ctrl;
-       Control target_speed;
+       float target_speed;
+       bool reverse;
+       bool pending_reverse;
        State state;
+       bool need_update;
 
 public:
-       AIControl(Train &, Controller *);
-       virtual ~AIControl();
+       AIControl(Train &);
 
-       virtual const char *enumerate_controls(unsigned) const;
-       virtual void set_control(const std::string &, float);
-       virtual const Control &get_control(const std::string &) const;
+       void set_target_speed(float);
+       float get_target_speed() const { return target_speed; }
+       void set_reverse(bool);
+       bool get_reverse() const { return reverse; } 
 
-       virtual float get_speed() const;
-       virtual bool get_reverse() const;
-       virtual float get_braking_distance() const;
-
-       virtual void tick(const Msp::Time::TimeDelta &);
+       virtual void message(const Message &);
+       virtual void tick(const Msp::Time::TimeStamp &, const Msp::Time::TimeDelta &);
 
 private:
-       void control_changed(const Control &);
        void arrived();
 };
 
index 4a78bcafadab289f5e2f88a4004d449b4e32fe06..c2a86a6e23db26d4da3dda44de08d79e715b7a2c 100644 (file)
@@ -20,7 +20,7 @@ using namespace Msp;
 namespace R2C2 {
 
 Timetable::Timetable(Train &t):
-       train(t),
+       TrainAI(t),
        enabled(false),
        current_row(0),
        executing(true),
@@ -76,7 +76,7 @@ const Timetable::Row &Timetable::get_row(unsigned i) const
        return rows[i];
 }
 
-void Timetable::tick(const Time::TimeStamp &t)
+void Timetable::tick(const Time::TimeStamp &t, const Time::TimeDelta &)
 {
        if(rows.empty() || !enabled)
                return;
@@ -138,10 +138,13 @@ void Timetable::tick(const Time::TimeStamp &t)
                        break;
                case SPEED:
                        if(!arrived)
-                               train.set_control("speed", row.get_param<unsigned>(0)/3.6*train.get_layout().get_catalogue().get_scale());
+                       {
+                               float speed = row.get_param<unsigned>(0)/3.6*train.get_layout().get_catalogue().get_scale();
+                               train.ai_message(Message("set-target-speed", speed));
+                       }
                        break;
                case REVERSE:
-                       train.set_control("reverse", !train.get_control("reverse"));
+                       train.ai_message(Message("toggle-reverse"));
                        break;
                case ROUTE:
                        if(!train.set_route(&train.get_layout().get_route(row.get_param<string>(0))))
@@ -156,6 +159,8 @@ void Timetable::tick(const Time::TimeStamp &t)
 
 void Timetable::save(list<DataFile::Statement> &st) const
 {
+       if(!tag.empty())
+               st.push_back((DataFile::Statement("tag"), tag));
        for(vector<Row>::const_iterator i=rows.begin(); i!=rows.end(); ++i)
                st.push_back(i->save());
 }
@@ -392,6 +397,7 @@ Timetable::Loader::Loader(Timetable &tt):
        add("route",       &Loader::route);
        add("speed",       &Loader::speed);
        add("reverse",     &Loader::reverse);
+       add("tag",         &Loader::tag);
        add("travel_to",   &Loader::travel_to);
        add("travel_past", &Loader::travel_past);
        add("wait",        &Loader::wait);
@@ -439,6 +445,11 @@ void Timetable::Loader::speed(unsigned s)
        obj.rows.push_back(Row(SPEED, s));
 }
 
+void Timetable::Loader::tag(const string &t)
+{
+       obj.tag = t;
+}
+
 void Timetable::Loader::travel_to(unsigned s)
 {
        obj.rows.push_back(Row(TRAVEL_TO, s));
index 96275c9bd65df85645beedbe9f3f72e6be95957e..1bf8f4fe6c0b000db6823edb1f699dc369010682 100644 (file)
@@ -12,7 +12,7 @@ Distributed under the GPL
 #include <vector>
 #include <sigc++/trackable.h>
 #include <msp/datafile/objectloader.h>
-#include <msp/time/timestamp.h>
+#include "trainai.h"
 
 namespace R2C2 {
 
@@ -21,7 +21,7 @@ class Track;
 class Train;
 class Zone;
 
-class Timetable: public sigc::trackable
+class Timetable: public TrainAI, public sigc::trackable
 {
 public:
        class Loader: public Msp::DataFile::ObjectLoader<Timetable>
@@ -36,6 +36,7 @@ public:
                void route(const std::string &);
                void reverse();
                void speed(unsigned);
+               void tag(const std::string &);
                void travel_to(unsigned);
                void travel_past(unsigned);
                void wait(unsigned);
@@ -79,7 +80,6 @@ public:
        };
 
 private:
-       Train &train;
        bool enabled;
        std::vector<Row> rows;
        unsigned current_row;
@@ -102,7 +102,7 @@ public:
        unsigned get_n_rows() const { return rows.size(); }
        const Row &get_row(unsigned) const;
 
-       void tick(const Msp::Time::TimeStamp &);
+       void tick(const Msp::Time::TimeStamp &, const Msp::Time::TimeDelta &);
        void save(std::list<Msp::DataFile::Statement> &) const;
 private:
        Track &get_sensor(unsigned);
index 16077ef8e4878823fe406e6098911e2ba6ee0e4b..38765cee7fde73517df49f546c5205bdd95caefa 100644 (file)
@@ -5,6 +5,7 @@ Copyright © 2006-2011  Mikkosoft Productions, Mikko Rasa
 Distributed under the GPL
 */
 
+#include <algorithm>
 #include <cmath>
 #include <msp/strings/formatter.h>
 #include <msp/time/units.h>
@@ -55,8 +56,7 @@ Train::Train(Layout &l, const VehicleType &t, unsigned a, const string &p):
        pending_block(0),
        reserving(false),
        advancing(false),
-       controller(new AIControl(*this, new SimpleController)),
-       timetable(0),
+       controller(new SimpleController),
        active(false),
        current_speed_step(0),
        speed_changing(false),
@@ -101,7 +101,6 @@ Train::Train(Layout &l, const VehicleType &t, unsigned a, const string &p):
 Train::~Train()
 {
        delete controller;
-       delete timetable;
        for(vector<Vehicle *>::iterator i=vehicles.begin(); i!=vehicles.end(); ++i)
                delete *i;
        layout.remove_train(*this);
@@ -209,10 +208,32 @@ bool Train::get_function(unsigned func) const
        return (functions>>func)&1;
 }
 
-void Train::set_timetable(Timetable *tt)
+void Train::add_ai(TrainAI &ai)
 {
-       delete timetable;
-       timetable = tt;
+       ais.push_back(&ai);
+       ai.signal_event.connect(sigc::bind<0>(signal_ai_event, sigc::ref(ai)));
+}
+
+void Train::remove_ai(TrainAI &ai)
+{
+       list<TrainAI *>::iterator i = find(ais.begin(), ais.end(), &ai);
+       if(i!=ais.end())
+               ais.erase(i);
+}
+
+TrainAI *Train::get_tagged_ai(const string &tag)
+{
+       for(list<TrainAI *>::iterator i=ais.begin(); i!=ais.end(); ++i)
+               if((*i)->get_tag()==tag)
+                       return *i;
+
+       return 0;
+}
+
+void Train::ai_message(const TrainAI::Message &msg)
+{
+       for(list<TrainAI *>::iterator i=ais.begin(); i!=ais.end(); ++i)
+               (*i)->message(msg);
 }
 
 bool Train::set_route(const Route *r)
@@ -559,8 +580,8 @@ void Train::tick(const Time::TimeStamp &t, const Time::TimeDelta &dt)
 
        Driver &driver = layout.get_driver();
 
-       if(timetable)
-               timetable->tick(t);
+       for(list<TrainAI *>::iterator i=ais.begin(); i!=ais.end(); ++i)
+               (*i)->tick(t, dt);
        controller->tick(dt);
        float speed = controller->get_speed();
 
@@ -679,12 +700,14 @@ void Train::save(list<DataFile::Statement> &st) const
                        st.push_back((DataFile::Statement("route"), i->route->get_name()));
        }
 
-       if(timetable)
-       {
-               DataFile::Statement ss("timetable");
-               timetable->save(ss.sub);
-               st.push_back(ss);
-       }
+       // XXX Need more generic way of saving AI state
+       for(list<TrainAI *>::const_iterator i=ais.begin(); i!=ais.end(); ++i)
+               if(Timetable *timetable = dynamic_cast<Timetable *>(*i))
+               {
+                       DataFile::Statement ss("timetable");
+                       timetable->save(ss.sub);
+                       st.push_back(ss);
+               }
 }
 
 void Train::control_changed(const Controller::Control &ctrl)
@@ -1379,11 +1402,8 @@ void Train::Loader::route(const string &n)
 
 void Train::Loader::timetable()
 {
-       if(obj.timetable)
-               throw InvalidState("A timetable has already been loaded");
-
-       obj.timetable = new Timetable(obj);
-       load_sub(*obj.timetable);
+       Timetable *ttbl = new Timetable(obj);
+       load_sub(*ttbl);
 }
 
 void Train::Loader::vehicle(ArticleNumber art_nr)
index b72fbf635c1d613c8e7f4aab07333dc4fb29eed3..59aa9dc022365be78823885a210f0431872dc48b 100644 (file)
@@ -14,13 +14,13 @@ Distributed under the GPL
 #include "block.h"
 #include "blockiter.h"
 #include "controller.h"
+#include "trainai.h"
 
 namespace R2C2 {
 
 class ArticleNumber;
 class Route;
 class SpeedQuantizer;
-class Timetable;
 class Vehicle;
 class VehicleType;
 class Zone;
@@ -50,6 +50,7 @@ public:
        sigc::signal<void, const std::string &> signal_name_changed;
        sigc::signal<void, const std::string &, float> signal_control_changed;
        sigc::signal<void, unsigned, bool> signal_function_changed;
+       sigc::signal<void, TrainAI &, const TrainAI::Message &> signal_ai_event;
        sigc::signal<void, const Route *> signal_route_changed;
        sigc::signal<void, Block &> signal_advanced;
        sigc::signal<void> signal_arrived;
@@ -82,7 +83,7 @@ private:
        bool reserving;
        bool advancing;
        Controller *controller;
-       Timetable *timetable;
+       std::list<TrainAI *> ais;
        bool active;
        unsigned current_speed_step;
        bool speed_changing;
@@ -131,8 +132,10 @@ public:
        bool get_function(unsigned) const;
        unsigned get_functions() const { return functions; }
 
-       void set_timetable(Timetable *);
-       Timetable *get_timetable() { return timetable; }
+       void add_ai(TrainAI &);
+       void remove_ai(TrainAI &);
+       TrainAI *get_tagged_ai(const std::string &);
+       void ai_message(const TrainAI::Message &);
 
        bool set_route(const Route *);
        bool go_to(Track &);
diff --git a/source/libr2c2/trainai.cpp b/source/libr2c2/trainai.cpp
new file mode 100644 (file)
index 0000000..a187216
--- /dev/null
@@ -0,0 +1,31 @@
+/* $Id$
+
+This file is part of R²C²
+Copyright © 2011  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
+#include "train.h"
+#include "trainai.h"
+
+using namespace std;
+
+namespace R2C2 {
+
+TrainAI::TrainAI(Train &t):
+       train(t)
+{
+       train.add_ai(*this);
+}
+
+TrainAI::~TrainAI()
+{
+       train.remove_ai(*this);
+}
+
+void TrainAI::set_tag(const string &t)
+{
+       tag = t;
+}
+
+} // namespace R2C2
diff --git a/source/libr2c2/trainai.h b/source/libr2c2/trainai.h
new file mode 100644 (file)
index 0000000..4f9d99c
--- /dev/null
@@ -0,0 +1,62 @@
+/* $Id$
+
+This file is part of R²C²
+Copyright © 2011  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
+#ifndef LIBR2C2_TRAINAI_H_
+#define LIBR2C2_TRAINAI_H_
+
+#include <string>
+#include <sigc++/signal.h>
+#include <msp/core/variant.h>
+#include <msp/time/timedelta.h>
+#include <msp/time/timestamp.h>
+
+namespace R2C2 {
+
+class Train;
+
+/**
+Base class for train AIs.
+
+AIs can help the user in various ways, ranging from automatically stopping the
+train at the end of allocated track to autonomously running a train.
+
+XXX The timestamp should be removed from tick, but Timetable depends on it
+*/
+class TrainAI
+{
+public:
+       struct Message
+       {
+               std::string type;
+               Msp::Variant value;
+
+               Message(const std::string &t): type(t) { }
+
+               template<typename T>
+               Message(const std::string &t, const T &v): type(t), value(v) { }
+       };
+
+       sigc::signal<void, const Message &> signal_event;
+
+protected:
+       Train &train;
+       std::string tag;
+
+       TrainAI(Train &);
+public:
+       virtual ~TrainAI();
+
+       void set_tag(const std::string &);
+       const std::string &get_tag() const { return tag; }
+
+       virtual void message(const Message &) { }
+       virtual void tick(const Msp::Time::TimeStamp &, const Msp::Time::TimeDelta &) { }
+};
+
+} // namespace R2C2
+
+#endif