/* $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"
{
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");
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)
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);
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)
/* $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
*/
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 &);
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;
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)
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
/* $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
*/
#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
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();
};
namespace R2C2 {
Timetable::Timetable(Train &t):
- train(t),
+ TrainAI(t),
enabled(false),
current_row(0),
executing(true),
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;
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))))
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());
}
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);
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));
#include <vector>
#include <sigc++/trackable.h>
#include <msp/datafile/objectloader.h>
-#include <msp/time/timestamp.h>
+#include "trainai.h"
namespace R2C2 {
class Train;
class Zone;
-class Timetable: public sigc::trackable
+class Timetable: public TrainAI, public sigc::trackable
{
public:
class Loader: public Msp::DataFile::ObjectLoader<Timetable>
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);
};
private:
- Train &train;
bool enabled;
std::vector<Row> rows;
unsigned current_row;
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);
Distributed under the GPL
*/
+#include <algorithm>
#include <cmath>
#include <msp/strings/formatter.h>
#include <msp/time/units.h>
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),
Train::~Train()
{
delete controller;
- delete timetable;
for(vector<Vehicle *>::iterator i=vehicles.begin(); i!=vehicles.end(); ++i)
delete *i;
layout.remove_train(*this);
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)
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();
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)
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)
#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;
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;
bool reserving;
bool advancing;
Controller *controller;
- Timetable *timetable;
+ std::list<TrainAI *> ais;
bool active;
unsigned current_speed_step;
bool speed_changing;
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 &);
--- /dev/null
+/* $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
--- /dev/null
+/* $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