From d3a7a9e9ad694d52ccca8b6038501772fdc1dfd5 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 5 Apr 2011 15:49:19 +0000 Subject: [PATCH] TrainAI framework Make AIControl an AI instead of a Controller Remote control is a bit broken right now --- source/engineer/trainpanel.cpp | 30 ++++----- source/engineer/trainpanel.h | 4 +- source/libr2c2/aicontrol.cpp | 120 ++++++++++++--------------------- source/libr2c2/aicontrol.h | 31 ++++----- source/libr2c2/timetable.cpp | 19 ++++-- source/libr2c2/timetable.h | 8 +-- source/libr2c2/train.cpp | 58 ++++++++++------ source/libr2c2/train.h | 11 +-- source/libr2c2/trainai.cpp | 31 +++++++++ source/libr2c2/trainai.h | 62 +++++++++++++++++ 10 files changed, 230 insertions(+), 144 deletions(-) create mode 100644 source/libr2c2/trainai.cpp create mode 100644 source/libr2c2/trainai.h diff --git a/source/engineer/trainpanel.cpp b/source/engineer/trainpanel.cpp index 5d237ec..813a534 100644 --- a/source/engineer/trainpanel.cpp +++ b/source/engineer/trainpanel.cpp @@ -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 #include +#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()/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()); } 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(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) diff --git a/source/engineer/trainpanel.h b/source/engineer/trainpanel.h index d74ccbf..87803ec 100644 --- a/source/engineer/trainpanel.h +++ b/source/engineer/trainpanel.h @@ -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 &); diff --git a/source/libr2c2/aicontrol.cpp b/source/libr2c2/aicontrol.cpp index e852858..e5bed9c 100644 --- a/source/libr2c2/aicontrol.cpp +++ b/source/libr2c2/aicontrol.cpp @@ -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()); + else if(msg.type=="set-reverse") + set_reverse(msg.value.value()); + 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 diff --git a/source/libr2c2/aicontrol.h b/source/libr2c2/aicontrol.h index 8d9ff11..01429cd 100644 --- a/source/libr2c2/aicontrol.h +++ b/source/libr2c2/aicontrol.h @@ -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 -#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(); }; diff --git a/source/libr2c2/timetable.cpp b/source/libr2c2/timetable.cpp index 4a78bca..c2a86a6 100644 --- a/source/libr2c2/timetable.cpp +++ b/source/libr2c2/timetable.cpp @@ -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(0)/3.6*train.get_layout().get_catalogue().get_scale()); + { + float speed = row.get_param(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(0)))) @@ -156,6 +159,8 @@ void Timetable::tick(const Time::TimeStamp &t) void Timetable::save(list &st) const { + if(!tag.empty()) + st.push_back((DataFile::Statement("tag"), tag)); for(vector::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)); diff --git a/source/libr2c2/timetable.h b/source/libr2c2/timetable.h index 96275c9..1bf8f4f 100644 --- a/source/libr2c2/timetable.h +++ b/source/libr2c2/timetable.h @@ -12,7 +12,7 @@ Distributed under the GPL #include #include #include -#include +#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 @@ -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 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 &) const; private: Track &get_sensor(unsigned); diff --git a/source/libr2c2/train.cpp b/source/libr2c2/train.cpp index 16077ef..38765ce 100644 --- a/source/libr2c2/train.cpp +++ b/source/libr2c2/train.cpp @@ -5,6 +5,7 @@ Copyright © 2006-2011 Mikkosoft Productions, Mikko Rasa Distributed under the GPL */ +#include #include #include #include @@ -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::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::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::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::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::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 &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::const_iterator i=ais.begin(); i!=ais.end(); ++i) + if(Timetable *timetable = dynamic_cast(*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) diff --git a/source/libr2c2/train.h b/source/libr2c2/train.h index b72fbf6..59aa9dc 100644 --- a/source/libr2c2/train.h +++ b/source/libr2c2/train.h @@ -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 signal_name_changed; sigc::signal signal_control_changed; sigc::signal signal_function_changed; + sigc::signal signal_ai_event; sigc::signal signal_route_changed; sigc::signal signal_advanced; sigc::signal signal_arrived; @@ -82,7 +83,7 @@ private: bool reserving; bool advancing; Controller *controller; - Timetable *timetable; + std::list 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 index 0000000..a187216 --- /dev/null +++ b/source/libr2c2/trainai.cpp @@ -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 index 0000000..4f9d99c --- /dev/null +++ b/source/libr2c2/trainai.h @@ -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 +#include +#include +#include +#include + +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 + Message(const std::string &t, const T &v): type(t), value(v) { } + }; + + sigc::signal 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 -- 2.45.2