Skip to content

Commit

Permalink
VehicleSpeedCurve: now it's a SubObject
Browse files Browse the repository at this point in the history
  • Loading branch information
gfgit committed Sep 14, 2024
1 parent 65ff801 commit 088aa03
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 56 deletions.
3 changes: 3 additions & 0 deletions client/src/widget/createwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "propertydoublespinbox.hpp"
#include "propertyspinbox.hpp"
#include "propertylineedit.hpp"
#include "vehiclespeedcurvewidget.hpp"
#include "../board/boardwidget.hpp"
#include "../network/object.hpp"
#include "../network/inputmonitor.hpp"
Expand Down Expand Up @@ -73,6 +74,8 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
return new ListWidget(object, parent);
else if(classId == "marklin_can_locomotive_list")
return new MarklinCANLocomotiveListWidget(object, parent);
else if(classId == "vehicle_speed_curve")
return new VehicleSpeedCurveWidget(object, parent);
else
return nullptr;
}
Expand Down
178 changes: 178 additions & 0 deletions client/src/widget/vehiclespeedcurvewidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include "vehiclespeedcurvewidget.hpp"

#include "../network/method.hpp"
#include "../network/error.hpp"
#include "../network/callmethod.hpp"
#include <traintastic/locale/locale.hpp>

#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QFileDialog>
#include <QMessageBox>
#include <QFile>

#include <QSettings>
#include <QStandardPaths>

#include <string>

VehicleSpeedCurveWidget::VehicleSpeedCurveWidget(ObjectPtr object,
QWidget *parent)
: QWidget(parent)
, m_object(object)
{
m_importMethod = m_object->getMethod("import_from_string");
m_exportMethod = m_object->getMethod("export_to_string");

QHBoxLayout *lay = new QHBoxLayout(this);

m_importBut = new QPushButton(Locale::tr("qtapp:import_speed_curve"));
lay->addWidget(m_importBut);

m_exportBut = new QPushButton(Locale::tr("qtapp:export_speed_curve"));
lay->addWidget(m_exportBut);

m_invalidCurveLabel = new QLabel(Locale::tr("qtapp:invalid_speed_curve"));
lay->addWidget(m_invalidCurveLabel);

if(m_importMethod)
{
m_importBut->setEnabled(m_importMethod->getAttributeBool(AttributeName::Enabled, true));
m_importBut->setVisible(m_importMethod->getAttributeBool(AttributeName::Visible, true));
connect(m_importMethod, &Method::attributeChanged, this,
[this](AttributeName name, const QVariant& value)
{
switch(name)
{
case AttributeName::Enabled:
m_importBut->setEnabled(value.toBool());
break;

case AttributeName::Visible:
m_importBut->setVisible(value.toBool());
break;

default:
break;
}
});

connect(m_importBut, &QPushButton::clicked,
this, &VehicleSpeedCurveWidget::importFromFile);
}

if(m_exportMethod)
{
m_exportBut->setEnabled(m_exportMethod->getAttributeBool(AttributeName::Enabled, true));
setCurveValid(m_exportMethod->getAttributeBool(AttributeName::Visible, true));
connect(m_exportMethod, &Method::attributeChanged, this,
[this](AttributeName name, const QVariant& value)
{
switch(name)
{
case AttributeName::Enabled:
m_exportBut->setEnabled(value.toBool());
break;

case AttributeName::Visible:
setCurveValid(value.toBool());
break;

default:
break;
}
});

connect(m_exportBut, &QPushButton::clicked,
this, &VehicleSpeedCurveWidget::exportToFile);
}
}

void VehicleSpeedCurveWidget::importFromFile()
{
QSettings settings;
settings.beginGroup("speed_curves");
const QString pathKey{"path"};

const QString filename = QFileDialog::getOpenFileName(
this,
Locale::tr("qtapp:import_speed_curve"),
settings.value(pathKey, QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first()).toString(),
Locale::tr("qtapp:traintastic_json_speed_curve").append(" (*.json)"));

if(!filename.isEmpty())
{
settings.setValue(pathKey, QFileInfo(filename).absolutePath());

QFile file(filename);
if(file.open(QIODevice::ReadOnly))
{
callMethod(*m_importMethod,
[this](std::optional<const Error> error)
{
if(error)
error->show(this);
},
QString::fromLocal8Bit(file.readAll()));
}
else
{
QMessageBox::critical(
this,
Locale::tr("qtapp:import_speed_curve_failed"),
Locale::tr("qtapp.error:cant_read_from_file_x").arg(filename));
}
}
}

void VehicleSpeedCurveWidget::exportToFile()
{
QSettings settings;
settings.beginGroup("speed_curves");
const QString pathKey{"path"};

const QString filename = QFileDialog::getSaveFileName(
this,
Locale::tr("qtapp:import_speed_curve"),
settings.value(pathKey, QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first()).toString(),
Locale::tr("qtapp:traintastic_json_speed_curve").append(" (*.json)"));

if(!filename.isEmpty())
{
settings.setValue(pathKey, QFileInfo(filename).absolutePath());

callMethodR<QString>(*m_exportMethod,
[this, filename](const QString& str, std::optional<const Error> error)
{
if(error)
{
error->show(this);
return;
}

bool success = false;
QFile file(filename);
if(file.open(QIODevice::WriteOnly))
{
qint64 n = file.write(str.toUtf8());
if(n == str.size())
success = true;
}

if(!success)
{
QMessageBox::critical(
this,
Locale::tr("qtapp:export_speed_curve_failed"),
Locale::tr("qtapp.error:cant_write_to_file_x").arg(filename));
}
});
}
}

void VehicleSpeedCurveWidget::setCurveValid(bool valid)
{
m_exportBut->setVisible(valid);
m_invalidCurveLabel->setVisible(!valid);
}
35 changes: 35 additions & 0 deletions client/src/widget/vehiclespeedcurvewidget.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef TRAINTASTIC_CLIENT_WIDGET_VEHICLE_SPEED_CURVE_WIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_VEHICLE_SPEED_CURVE_WIDGET_HPP

#include <QWidget>
#include "../network/objectptr.hpp"

class Method;
class QPushButton;
class QLabel;

class VehicleSpeedCurveWidget : public QWidget
{
Q_OBJECT

private:
ObjectPtr m_object;
Method *m_importMethod;
Method *m_exportMethod;

QPushButton *m_importBut;
QPushButton *m_exportBut;
QLabel *m_invalidCurveLabel;

private slots:
void importFromFile();
void exportToFile();

private:
void setCurveValid(bool valid);

public:
explicit VehicleSpeedCurveWidget(ObjectPtr object, QWidget* parent = nullptr);
};

#endif // TRAINTASTIC_CLIENT_WIDGET_VEHICLE_SPEED_CURVE_WIDGET_HPP
14 changes: 8 additions & 6 deletions server/src/train/train.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
#include "../utils/almostzero.hpp"
#include "../utils/displayname.hpp"

#include <memory>

CREATE_IMPL(Train)

static inline bool isPowered(const RailVehicle& vehicle)
Expand Down Expand Up @@ -553,18 +555,18 @@ void Train::updateSpeedTable()
return;
}

std::vector<VehicleSpeedCurve> speedCurves;
std::vector<std::shared_ptr<VehicleSpeedCurve>> speedCurves;
speedCurves.reserve(m_poweredVehicles.size());
for(const auto& vehicle : m_poweredVehicles)
for(const std::shared_ptr<PoweredRailVehicle>& vehicle : m_poweredVehicles)
{
if(!vehicle->m_speedCurve)
if(!vehicle->speedCurve->isValid())
{
// All vehicles must have a speed curve loaded
m_speedTable.reset();
return;
}

speedCurves.push_back(*vehicle->m_speedCurve);
speedCurves.push_back(vehicle->speedCurve.value());
}

if(!m_speedTable)
Expand Down Expand Up @@ -707,10 +709,10 @@ void Train::updateSpeedMax()
{
for(const auto& item : *vehicles)
{
if(m_speedTable && isPowered(item.vehicle))
if(m_speedTable && isPowered(*item->vehicle))
continue; // Already taken into account by speed table max

const SpeedProperty& vehicleMax = item.vehicle->speedMax;
const SpeedProperty& vehicleMax = item->vehicle->speedMax;
const double v = vehicleMax.getValue(SpeedUnit::MeterPerSecond);

if((v > 0 && v < realMaxSpeedMS) || !speedWasAdjusted)
Expand Down
14 changes: 7 additions & 7 deletions server/src/train/trainspeedtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const TrainSpeedTable::Entry& TrainSpeedTable::getEntryAt(uint8_t idx) const
return mEntries.at(idx - 1);
}

TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<VehicleSpeedCurve> &locoMappings)
TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<std::shared_ptr<VehicleSpeedCurve>> &locoMappings)
{
// TODO: prefer pushing/pulling

Expand All @@ -133,7 +133,7 @@ TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<VehicleSpeedCurve>
{
// Special case: only 1 locomotive
// Table is a replica of locomotive speed curve
const VehicleSpeedCurve& speedCurve = locoMappings.at(0);
const VehicleSpeedCurve& speedCurve = *locoMappings.at(0);

table.mEntries.reserve(126);
for(int step = 1; step <= 126; step++)
Expand All @@ -149,10 +149,10 @@ TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<VehicleSpeedCurve>
return table;
}

double maxTrainSpeed = locoMappings.at(0).getSpeedForStep(126);
double maxTrainSpeed = locoMappings.at(0)->getSpeedForStep(126);
for(uint32_t locoIdx = 1; locoIdx < NUM_LOCOS; locoIdx++)
{
double maxSpeed = locoMappings.at(locoIdx).getSpeedForStep(126);
double maxSpeed = locoMappings.at(locoIdx)->getSpeedForStep(126);
if(maxSpeed < maxTrainSpeed)
maxTrainSpeed = maxSpeed;
}
Expand Down Expand Up @@ -182,7 +182,7 @@ TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<VehicleSpeedCurve>
entries.reserve(200);
diffVector.reserve(200);

uint8_t firstLocoMaxStep = locoMappings.at(0).stepLowerBound(maxTrainSpeed);
uint8_t firstLocoMaxStep = locoMappings.at(0)->stepLowerBound(maxTrainSpeed);
if(firstLocoMaxStep == 0)
firstLocoMaxStep = 126;

Expand All @@ -193,7 +193,7 @@ TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<VehicleSpeedCurve>

while(stepCache[0].currentStep <= firstLocoMaxStep)
{
const VehicleSpeedCurve& mapping = locoMappings.at(currentLocoIdx);
const VehicleSpeedCurve& mapping = *locoMappings.at(currentLocoIdx);
LocoStepCache& item = stepCache[currentLocoIdx];

if(currentLocoIdx == 0)
Expand All @@ -207,7 +207,7 @@ TrainSpeedTable TrainSpeedTable::buildTable(const std::vector<VehicleSpeedCurve>

for(uint32_t otherLocoIdx = 1; otherLocoIdx < locoMappings.size(); otherLocoIdx++)
{
const VehicleSpeedCurve& otherMapping = locoMappings.at(otherLocoIdx);
const VehicleSpeedCurve& otherMapping = *locoMappings.at(otherLocoIdx);
LocoStepCache& otherItem = stepCache[otherLocoIdx];

otherItem.minAcceptedStep = otherMapping.stepLowerBound(minAcceptedSpeed);
Expand Down
2 changes: 1 addition & 1 deletion server/src/train/trainspeedtable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class TrainSpeedTable

const Entry& getEntryAt(uint8_t tableIdx) const;

static TrainSpeedTable buildTable(const std::vector<VehicleSpeedCurve>& locoMappings);
static TrainSpeedTable buildTable(const std::vector<std::shared_ptr<VehicleSpeedCurve> > &locoMappings);

private:
std::vector<Entry> mEntries;
Expand Down
2 changes: 2 additions & 0 deletions server/src/train/trainvehiclelist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ void TrainVehicleList::propertyChanged(BaseProperty &property)
train().updateWeight();
else if(property.name() == "speed_max")
train().updateSpeedMax();
else if(property.name() == "speed_curve")
train().updatePowered();

if(!m_models.empty() && TrainVehicleListTableModel::isListedProperty(property.name()))
{
Expand Down
23 changes: 8 additions & 15 deletions server/src/vehicle/rail/poweredrailvehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,17 @@
PoweredRailVehicle::PoweredRailVehicle(World& world, std::string_view id_)
: RailVehicle(world, id_)
, power{*this, "power", 0, PowerUnit::KiloWatt, PropertyFlags::ReadWrite | PropertyFlags::Store}
, importSpeedCurve{*this, "import_speed_curve", MethodFlags::ScriptCallable,
[this](const std::string& str)
{
if(!m_speedCurve)
m_speedCurve.reset(new VehicleSpeedCurve);

if(!m_speedCurve->loadFromString(str))
m_speedCurve.reset();
}}
, speedCurve{this, "speed_curve", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}
{
const bool editable = contains(m_world.state.value(), WorldState::Edit);

Attributes::addDisplayName(power, DisplayName::Vehicle::Rail::power);
Attributes::addEnabled(power, editable);
m_interfaceItems.add(power);

Attributes::addDisplayName(importSpeedCurve, "import_speed_curve");
Attributes::addEnabled(importSpeedCurve, true);
Attributes::addVisible(importSpeedCurve, true);
m_interfaceItems.add(importSpeedCurve);
speedCurve.setValueInternal(std::make_shared<VehicleSpeedCurve>(*this, speedCurve.name()));
Attributes::addEnabled(speedCurve, true);
m_interfaceItems.add(speedCurve);

propertyChanged.connect(
[this](BaseProperty &prop)
Expand Down Expand Up @@ -110,10 +101,12 @@ void PoweredRailVehicle::worldEvent(WorldState state, WorldEvent event)

void PoweredRailVehicle::updateMaxSpeed()
{
if(!m_speedCurve)
propertyChanged(speedCurve); //TODO: find better way

if(!speedCurve->isValid())
return;

double speedMS = m_speedCurve->getSpeedForStep(126);
double speedMS = speedCurve->getSpeedForStep(126);
speedMS *= m_world.scaleRatio;

speedMax.setValueInternal(convertUnit(speedMS,
Expand Down
Loading

0 comments on commit 088aa03

Please sign in to comment.