From 1d6929bc170354e2e8f2697a2da49bcf37bb95f1 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 6 Oct 2021 22:22:33 +0200 Subject: [PATCH 01/43] board: shift right click now rotates counter clockwise --- client/src/board/boardwidget.cpp | 5 ++++- shared/src/traintastic/enum/tilerotate.hpp | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/client/src/board/boardwidget.cpp b/client/src/board/boardwidget.cpp index c341097f..8d7b106e 100644 --- a/client/src/board/boardwidget.cpp +++ b/client/src/board/boardwidget.cpp @@ -519,7 +519,10 @@ void BoardWidget::rightClicked() if(QAction* act = m_editActions->checkedAction()) if(int index = act->data().toInt(); index >= 0 && Q_LIKELY(index < tileInfo.size())) { - m_editRotate += TileRotate::Deg45; + if(QApplication::keyboardModifiers() == Qt::NoModifier) + m_editRotate += TileRotate::Deg45; + else if(QApplication::keyboardModifiers() == Qt::ShiftModifier) + m_editRotate -= TileRotate::Deg45; validRotate(m_editRotate, tileInfo[index].rotates); m_boardArea->setMouseMoveTileRotate(m_editRotate); } diff --git a/shared/src/traintastic/enum/tilerotate.hpp b/shared/src/traintastic/enum/tilerotate.hpp index 8091058b..2b466b23 100644 --- a/shared/src/traintastic/enum/tilerotate.hpp +++ b/shared/src/traintastic/enum/tilerotate.hpp @@ -66,7 +66,13 @@ constexpr TileRotate& operator +=(TileRotate& lhs, TileRotate rhs) constexpr TileRotate operator -(TileRotate lhs, TileRotate rhs) { - return static_cast((static_cast>(lhs) + 8 - static_cast>(rhs)) % 8); + return static_cast(((static_cast>(lhs) + 8 - (static_cast>(rhs)) % 8)) % 8); +} + +constexpr TileRotate& operator -=(TileRotate& lhs, TileRotate rhs) +{ + lhs = lhs - rhs; + return lhs; } constexpr bool isDiagonal(TileRotate value) From dd08ede080cc5937f1eaebb600fbe2f82a6b55e6 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 6 Oct 2021 22:23:09 +0200 Subject: [PATCH 02/43] board: allow placing signal on top of straght if direction matches --- server/src/board/board.cpp | 36 ++++++++++++++++++++++----------- server/src/board/tile/tiles.cpp | 7 +++++++ server/src/board/tile/tiles.hpp | 2 ++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/server/src/board/board.cpp b/server/src/board/board.cpp index eb7a14f9..e7a5de92 100644 --- a/server/src/board/board.cpp +++ b/server/src/board/board.cpp @@ -46,23 +46,35 @@ Board::Board(const std::weak_ptr& world, std::string_view _id) : if(auto it = m_tiles.find(l); it != m_tiles.end()) { - if(!replace && tileClassId == StraightRailTile::classId && it->second->tileId() == TileId::RailStraight) // merge to bridge + if(!replace) { const TileRotate tileRotate = it->second->rotate; - if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y)) + + if(tileClassId == StraightRailTile::classId && it->second->tileId() == TileId::RailStraight) // merge to bridge { - tileClassId = Bridge90RailTile::classId; - rotate = tileRotate; + if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y)) + { + tileClassId = Bridge90RailTile::classId; + rotate = tileRotate; + } + else if((tileRotate == rotate + TileRotate::Deg45 || tileRotate == rotate + TileRotate::Deg225) && deleteTile(x, y)) + { + tileClassId = Bridge45LeftRailTile::classId; + rotate = tileRotate; + } + else if((tileRotate == rotate - TileRotate::Deg45 || tileRotate == rotate - TileRotate::Deg225) && deleteTile(x, y)) + { + tileClassId = Bridge45RightRailTile::classId; + rotate = tileRotate; + } + else + return false; } - else if((tileRotate == rotate + TileRotate::Deg45 || tileRotate == rotate + TileRotate::Deg225) && deleteTile(x, y)) + else if(Tiles::isRailSignal(tileClassId) && // replace straight by a signal + it->second->tileId() == TileId::RailStraight && + (tileRotate == rotate || (tileRotate + TileRotate::Deg180) == rotate) && deleteTile(x, y)) { - tileClassId = Bridge45LeftRailTile::classId; - rotate = tileRotate; - } - else if((tileRotate == rotate - TileRotate::Deg45 || tileRotate == rotate - TileRotate::Deg225) && deleteTile(x, y)) - { - tileClassId = Bridge45RightRailTile::classId; - rotate = tileRotate; + // tileClassId and rotate are ok :) } else return false; diff --git a/server/src/board/tile/tiles.cpp b/server/src/board/tile/tiles.cpp index 9a18eaec..9912cebc 100644 --- a/server/src/board/tile/tiles.cpp +++ b/server/src/board/tile/tiles.cpp @@ -52,3 +52,10 @@ std::shared_ptr Tiles::create(const std::shared_ptr& world, std::st IF_CLASSID_CREATE(TunnelRailTile) return std::shared_ptr(); } + +bool Tiles::isRailSignal(std::string_view classId) +{ + return + (classId == Signal2AspectRailTile::classId) || + (classId == Signal3AspectRailTile::classId); +} diff --git a/server/src/board/tile/tiles.hpp b/server/src/board/tile/tiles.hpp index f158f4ee..e28923dc 100644 --- a/server/src/board/tile/tiles.hpp +++ b/server/src/board/tile/tiles.hpp @@ -82,6 +82,8 @@ struct Tiles ); static std::shared_ptr create(const std::shared_ptr& world, std::string_view classId, std::string_view id = {}); + + static bool isRailSignal(std::string_view classId); }; #endif From 8ac1c4d2ae363b4e7a2cfe63508c5a45a1a7eef8 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Tue, 12 Oct 2021 22:13:40 +0200 Subject: [PATCH 03/43] fix: corrected class id, caused buffer stop not to be buildable --- server/src/board/tile/rail/bufferstoprailtile.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/board/tile/rail/bufferstoprailtile.hpp b/server/src/board/tile/rail/bufferstoprailtile.hpp index b0cb97ed..202407bf 100644 --- a/server/src/board/tile/rail/bufferstoprailtile.hpp +++ b/server/src/board/tile/rail/bufferstoprailtile.hpp @@ -27,7 +27,7 @@ class BufferStopRailTile : public RailTile { - CLASS_ID("board_tile.rail.bufferstop") + CLASS_ID("board_tile.rail.buffer_stop") CREATE(BufferStopRailTile) public: From 48c32708d1c85589b6e09cde3690b27a18ecea64 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Tue, 12 Oct 2021 22:14:57 +0200 Subject: [PATCH 04/43] board: straight can now also be upgraded to a tunnel, block or sensor --- server/src/board/board.cpp | 9 +++++---- server/src/board/tile/tiles.cpp | 5 ++++- server/src/board/tile/tiles.hpp | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/server/src/board/board.cpp b/server/src/board/board.cpp index e7a5de92..d4bc5238 100644 --- a/server/src/board/board.cpp +++ b/server/src/board/board.cpp @@ -50,7 +50,7 @@ Board::Board(const std::weak_ptr& world, std::string_view _id) : { const TileRotate tileRotate = it->second->rotate; - if(tileClassId == StraightRailTile::classId && it->second->tileId() == TileId::RailStraight) // merge to bridge + if(it->second->tileId() == TileId::RailStraight && tileClassId == StraightRailTile::classId) // merge to bridge { if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y)) { @@ -70,9 +70,10 @@ Board::Board(const std::weak_ptr& world, std::string_view _id) : else return false; } - else if(Tiles::isRailSignal(tileClassId) && // replace straight by a signal - it->second->tileId() == TileId::RailStraight && - (tileRotate == rotate || (tileRotate + TileRotate::Deg180) == rotate) && deleteTile(x, y)) + else if(it->second->tileId() == TileId::RailStraight && // replace straight by a straight with something extra + Tiles::canUpgradeStraightRail(tileClassId) && + (tileRotate == rotate || (tileRotate + TileRotate::Deg180) == rotate) && + deleteTile(x, y)) { // tileClassId and rotate are ok :) } diff --git a/server/src/board/tile/tiles.cpp b/server/src/board/tile/tiles.cpp index 9912cebc..ebdaa34f 100644 --- a/server/src/board/tile/tiles.cpp +++ b/server/src/board/tile/tiles.cpp @@ -53,9 +53,12 @@ std::shared_ptr Tiles::create(const std::shared_ptr& world, std::st return std::shared_ptr(); } -bool Tiles::isRailSignal(std::string_view classId) +bool Tiles::canUpgradeStraightRail(std::string_view classId) { return + (classId == TunnelRailTile::classId) || + (classId == BlockRailTile::classId) || + (classId == SensorRailTile::classId) || (classId == Signal2AspectRailTile::classId) || (classId == Signal3AspectRailTile::classId); } diff --git a/server/src/board/tile/tiles.hpp b/server/src/board/tile/tiles.hpp index e28923dc..f10dbd57 100644 --- a/server/src/board/tile/tiles.hpp +++ b/server/src/board/tile/tiles.hpp @@ -83,7 +83,7 @@ struct Tiles static std::shared_ptr create(const std::shared_ptr& world, std::string_view classId, std::string_view id = {}); - static bool isRailSignal(std::string_view classId); + static bool canUpgradeStraightRail(std::string_view classId); }; #endif From 15fb96ff7ae8ca005b1c90dd4d8984f04b566a80 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Fri, 15 Oct 2021 23:21:44 +0200 Subject: [PATCH 05/43] board: added color schemes dark/light and two options for drawing turnouts --- client/src/board/boardareawidget.cpp | 29 ++++++++-- client/src/board/boardareawidget.hpp | 5 ++ client/src/board/boardcolorscheme.cpp | 31 +++++++++++ client/src/board/boardcolorscheme.hpp | 25 +++++++++ client/src/board/tilepainter.cpp | 60 +++++++++++---------- client/src/board/tilepainter.hpp | 28 +++++----- client/src/settings/boardsettings.hpp | 26 +++++++++ client/src/settings/boardsettingswidget.cpp | 2 + client/src/settings/setting.hpp | 6 +++ client/src/settings/settingsbase.hpp | 22 ++++++-- client/src/settings/settingsbasewidget.hpp | 23 ++++++++ client/src/utils/enum.cpp | 2 + shared/translations/en-us.txt | 5 ++ 13 files changed, 213 insertions(+), 51 deletions(-) create mode 100644 client/src/board/boardcolorscheme.cpp create mode 100644 client/src/board/boardcolorscheme.hpp diff --git a/client/src/board/boardareawidget.cpp b/client/src/board/boardareawidget.cpp index 58e4b3f9..16c82239 100644 --- a/client/src/board/boardareawidget.cpp +++ b/client/src/board/boardareawidget.cpp @@ -59,6 +59,7 @@ constexpr QRect updateTileRect(const int x, const int y, const int w, const int BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) : QWidget(parent), + m_colorScheme{&BoardColorScheme::dark}, m_board{board}, m_boardLeft{board.board().getProperty("left")}, m_boardTop{board.board().getProperty("top")}, @@ -84,11 +85,12 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) : if(Q_LIKELY(m_boardBottom)) connect(m_boardBottom, &AbstractProperty::valueChanged, this, &BoardAreaWidget::updateMinimumSize); - connect(&BoardSettings::instance(), &SettingsBase::changed, this, qOverload<>(&QWidget::update)); + connect(&BoardSettings::instance(), &SettingsBase::changed, this, &BoardAreaWidget::settingsChanged); for(const auto& [l, object] : m_board.board().tileObjects()) tileObjectAdded(l.x, l.y, object); + settingsChanged(); updateMinimumSize(); } @@ -374,8 +376,9 @@ void BoardAreaWidget::wheelEvent(QWheelEvent* event) void BoardAreaWidget::paintEvent(QPaintEvent* event) { + assert(m_colorScheme); + const bool showBlockSensorStates = BoardSettings::instance().showBlockSensorStates; - const QColor backgroundColor = TilePainter::backgroundColor; const QColor backgroundColor50{0x10, 0x10, 0x10, 0x80}; const QColor backgroundColorError50{0xff, 0x00, 0x00, 0x80}; const QColor gridColor{0x40, 0x40, 0x40}; @@ -388,7 +391,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event) const QRect viewport = rectToViewport(event->rect(), gridSize); - painter.fillRect(viewport, backgroundColor); + painter.fillRect(viewport, m_colorScheme->background); // draw grid: switch(m_grid) @@ -415,7 +418,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event) painter.setRenderHint(QPainter::Antialiasing, true); // draw tiles: - TilePainter tilePainter{painter, tileSize}; + TilePainter tilePainter{painter, tileSize, *m_colorScheme}; const int tileOriginX = boardLeft(); const int tileOriginY = boardTop(); @@ -534,6 +537,24 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event) } } +void BoardAreaWidget::settingsChanged() +{ + const auto& s = BoardSettings::instance(); + + switch(s.colorScheme.value()) + { + case BoardSettings::ColorScheme::Dark: + m_colorScheme = &BoardColorScheme::dark; + break; + + case BoardSettings::ColorScheme::Light: + m_colorScheme = &BoardColorScheme::light; + break; + } + + update(); +} + void BoardAreaWidget::updateMinimumSize() { const int tileSize = getTileSize() - 1; diff --git a/client/src/board/boardareawidget.hpp b/client/src/board/boardareawidget.hpp index 85a4342a..5042ddd1 100644 --- a/client/src/board/boardareawidget.hpp +++ b/client/src/board/boardareawidget.hpp @@ -32,6 +32,7 @@ #include #include #include +#include "boardcolorscheme.hpp" #include "../network/abstractproperty.hpp" #include "../network/objectptr.hpp" @@ -57,6 +58,9 @@ class BoardAreaWidget : public QWidget ResizeTile, }; + private: + const BoardColorScheme* m_colorScheme; + protected: static constexpr int boardMargin = 1; // tile @@ -105,6 +109,7 @@ class BoardAreaWidget : public QWidget void paintEvent(QPaintEvent* event) final; protected slots: + void settingsChanged(); void updateMinimumSize(); public: diff --git a/client/src/board/boardcolorscheme.cpp b/client/src/board/boardcolorscheme.cpp new file mode 100644 index 00000000..04917460 --- /dev/null +++ b/client/src/board/boardcolorscheme.cpp @@ -0,0 +1,31 @@ +#include "boardcolorscheme.hpp" + +const BoardColorScheme BoardColorScheme::dark = { + /*.background =*/ {0x10, 0x10, 0x10}, + /*.track =*/ {0xC0, 0xC0, 0xC0}, + /*.trackDisabled =*/ {0x40, 0x40, 0x40}, + /*.blockFree =*/ {0x66, 0xC6, 0x66}, + /*.blockOccupied =*/ {0xC6, 0x66, 0x66}, + /*.blockUnknown =*/ {0x66, 0x66, 0x66}, + /*.sensorFree =*/ {0x66, 0xC6, 0x66}, + /*.sensorOccupied =*/ {0xC6, 0x66, 0x66}, + /*.sensorIdle =*/ {0x40, 0x40, 0x40}, + /*.sensorTriggered =*/ {0x00, 0xBF, 0xFF}, + /*.sensorUnknown =*/ {0x10, 0x10, 0x10}, + /*.turnoutState =*/ {Qt::blue}, +}; + +const BoardColorScheme BoardColorScheme::light = { + /*.background =*/ {0xF5, 0xF5, 0xF5}, + /*.track =*/ {0x00, 0x00, 0x00}, + /*.trackDisabled =*/ {0xA0, 0xA0, 0xA0}, + /*.blockFree =*/ {0x44, 0xC6, 0x44}, + /*.blockOccupied =*/ {0xC6, 0x44, 0x44}, + /*.blockUnknown =*/ {0x88, 0x88, 0x88}, + /*.sensorFree =*/ {0x44, 0xC6, 0x44}, + /*.sensorOccupied =*/ {0xC6, 0x44, 0x44}, + /*.sensorIdle =*/ {0x40, 0x40, 0x40}, + /*.sensorTriggered =*/ {0x00, 0xBF, 0xFF}, + /*.sensorUnknown =*/ {0x10, 0x10, 0x10}, + /*.turnoutState =*/ {Qt::cyan}, +}; diff --git a/client/src/board/boardcolorscheme.hpp b/client/src/board/boardcolorscheme.hpp new file mode 100644 index 00000000..a9fccb11 --- /dev/null +++ b/client/src/board/boardcolorscheme.hpp @@ -0,0 +1,25 @@ +#ifndef ASD +#define ASD + +#include + +struct BoardColorScheme +{ + static const BoardColorScheme dark; + static const BoardColorScheme light; + + const QColor background; + const QColor track; + const QColor trackDisabled; + const QColor blockFree; + const QColor blockOccupied; + const QColor blockUnknown; + const QColor sensorFree; + const QColor sensorOccupied; + const QColor sensorIdle; + const QColor sensorTriggered; + const QColor sensorUnknown; + const QColor turnoutState; +}; + +#endif diff --git a/client/src/board/tilepainter.cpp b/client/src/board/tilepainter.cpp index e62bfb92..1b06dbf7 100644 --- a/client/src/board/tilepainter.cpp +++ b/client/src/board/tilepainter.cpp @@ -24,14 +24,20 @@ #include #include #include +#include "boardcolorscheme.hpp" +#include "../settings/boardsettings.hpp" #include "../utils/rectf.hpp" -TilePainter::TilePainter(QPainter& painter, int tileSize) : +TilePainter::TilePainter(QPainter& painter, int tileSize, const BoardColorScheme& colorScheme) : + m_colorScheme{colorScheme}, + m_turnoutDrawState{BoardSettings::instance().turnoutDrawState}, m_trackWidth{tileSize / 5}, m_turnoutMargin{tileSize / 10}, - m_trackPen(trackColor, m_trackWidth, Qt::SolidLine, Qt::FlatCap), - m_trackErasePen(backgroundColor, m_trackWidth * 2, Qt::SolidLine, Qt::FlatCap), - m_turnoutStatePen(Qt::blue, (m_trackWidth + 1) / 2, Qt::SolidLine, Qt::FlatCap), + m_blockPen{m_colorScheme.track}, + m_trackPen(m_colorScheme.track, m_trackWidth, Qt::SolidLine, Qt::FlatCap), + m_trackDisabledPen(m_colorScheme.trackDisabled, m_trackWidth, Qt::SolidLine, Qt::FlatCap), + m_trackErasePen(m_colorScheme.background, m_trackWidth * 2, Qt::SolidLine, Qt::FlatCap), + m_turnoutStatePen(m_colorScheme.turnoutState, (m_trackWidth + 1) / 2, Qt::SolidLine, Qt::FlatCap), m_painter{painter} { } @@ -136,9 +142,9 @@ void TilePainter::draw(TileId id, const QRectF& r, TileRotate rotate) const qreal m = r.width() / 5; const QRectF rArc = r.adjusted(m, m, -m, -m); - m_painter.setPen(QPen(backgroundColor, m_trackWidth, Qt::SolidLine, Qt::FlatCap)); + m_painter.setPen(QPen(m_colorScheme.background, m_trackWidth, Qt::SolidLine, Qt::FlatCap)); m_painter.drawArc(rArc, angle, angleLength); - m_painter.setPen(QPen(trackColor, m_trackWidth / 2., Qt::SolidLine, Qt::FlatCap)); + m_painter.setPen(QPen(m_colorScheme.track, m_trackWidth / 2., Qt::SolidLine, Qt::FlatCap)); m_painter.drawArc(rArc, angle, angleLength); break; } @@ -157,7 +163,7 @@ void TilePainter::drawSensor(TileId id, const QRectF& r, TileRotate rotate, Sens setTrackPen(); drawStraight(r, rotate); const qreal sz = r.width() / 4; - drawLED(r.adjusted(sz, sz, -sz, -sz), sensorStateToColor(state), trackColor); + drawLED(r.adjusted(sz, sz, -sz, -sz), sensorStateToColor(state), m_colorScheme.track); break; } default: @@ -170,7 +176,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur switch(id) { case TileId::RailTurnoutLeft45: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawCurve45(r, rotate); @@ -191,7 +197,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutLeft90: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawCurve90(r, rotate); @@ -212,7 +218,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutLeftCurved: - setTrackPen(); + setTurnoutPen(); drawCurve45(r, rotate); drawCurve90(r, rotate); @@ -233,7 +239,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutRight45: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawCurve45(r, rotate + TileRotate::Deg225); @@ -254,7 +260,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutRight90: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawCurve90(r, rotate + TileRotate::Deg270); @@ -275,7 +281,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutRightCurved: - setTrackPen(); + setTurnoutPen(); drawCurve45(r, rotate + TileRotate::Deg225); drawCurve90(r, rotate + TileRotate::Deg270); @@ -296,7 +302,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutWye: - setTrackPen(); + setTurnoutPen(); drawCurve45(r, rotate); drawCurve45(r, rotate + TileRotate::Deg225); @@ -317,7 +323,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnout3Way: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawCurve45(r, rotate); drawCurve45(r, rotate + TileRotate::Deg225); @@ -343,7 +349,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutSingleSlip: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawStraight(r, rotate - TileRotate::Deg45); drawCurve45(r, rotate); @@ -366,7 +372,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur break; case TileId::RailTurnoutDoubleSlip: - setTrackPen(); + setTurnoutPen(); drawStraight(r, rotate); drawStraight(r, rotate - TileRotate::Deg45); drawCurve45(r, rotate); @@ -439,19 +445,19 @@ QColor TilePainter::sensorStateToColor(SensorState value) const switch(value) { case SensorState::Occupied: - return sensorColorOccupied; + return m_colorScheme.sensorOccupied; case SensorState::Free: - return sensorColorFree; + return m_colorScheme.sensorFree; case SensorState::Idle: - return sensorColorIdle; + return m_colorScheme.sensorIdle; case SensorState::Triggered: - return sensorColorTriggered; + return m_colorScheme.sensorTriggered; case SensorState::Unknown: - return sensorColorUnknown; + return m_colorScheme.sensorUnknown; } assert(false); return QColor(); @@ -462,15 +468,15 @@ void TilePainter::setBlockStateBrush(BlockState value) switch(value) { case BlockState::Occupied: - m_painter.setBrush(blockBrushOccupied); + m_painter.setBrush(m_colorScheme.blockOccupied); break; case BlockState::Free: - m_painter.setBrush(blockBrushFree); + m_painter.setBrush(m_colorScheme.blockFree); break; case BlockState::Unknown: - m_painter.setBrush(blockBrushUnknown); + m_painter.setBrush(m_colorScheme.blockUnknown); break; } } @@ -768,7 +774,7 @@ void TilePainter::drawRailBlock(const QRectF& r, TileRotate rotate, BlockState s { m_painter.drawLine(topCenter(r), bottomCenter(r)); setBlockStateBrush(state); - m_painter.setPen(blockPen); + m_painter.setPen(m_blockPen); const qreal m = 0.5 + qFloor(r.width() / 10); const QRectF block = r.adjusted(m, m, -m, -m); m_painter.drawRect(block); @@ -790,7 +796,7 @@ void TilePainter::drawRailBlock(const QRectF& r, TileRotate rotate, BlockState s { m_painter.drawLine(centerLeft(r), centerRight(r)); setBlockStateBrush(state); - m_painter.setPen(blockPen); + m_painter.setPen(m_blockPen); const qreal m = 0.5 + qFloor(r.height() / 10); const QRectF block = r.adjusted(m, m, -m, -m); m_painter.drawRect(block); diff --git a/client/src/board/tilepainter.hpp b/client/src/board/tilepainter.hpp index 36946996..9d231e5b 100644 --- a/client/src/board/tilepainter.hpp +++ b/client/src/board/tilepainter.hpp @@ -34,38 +34,34 @@ #include #include +struct BoardColorScheme; + class TilePainter { - public: - inline static const QColor backgroundColor{0x10, 0x10, 0x10}; - private: - inline static const QColor trackColor{0xC0, 0xC0, 0xC0}; inline static const QColor signalRed{192, 0, 0}; inline static const QColor signalYellow{192, 192, 32}; inline static const QColor signalGreen{0, 192, 0}; - inline static const QBrush blockBrushFree{QColor{0x66, 0xC6, 0x66}}; - inline static const QBrush blockBrushOccupied{QColor{0xC6, 0x66, 0x66}}; - inline static const QBrush blockBrushUnknown{QColor{0x66, 0x66, 0x66}}; - inline static const QColor sensorColorFree{0x66, 0xC6, 0x66}; - inline static const QColor sensorColorOccupied{0xC6, 0x66, 0x66}; - inline static const QColor sensorColorIdle{0x40, 0x40, 0x40}; - inline static const QColor sensorColorTriggered{0x00, 0xBF, 0xFF}; - inline static const QColor sensorColorUnknown{0x10, 0x10, 0x10}; - inline static const QPen blockPen{trackColor}; + const BoardColorScheme& m_colorScheme; + const bool m_turnoutDrawState; const int m_trackWidth; const int m_turnoutMargin; + const QPen m_blockPen; const QPen m_trackPen; + const QPen m_trackDisabledPen; const QPen m_trackErasePen; + const QPen m_turnoutPen; const QPen m_turnoutStatePen; QPainter& m_painter; inline void setTrackPen() { m_painter.setPen(m_trackPen); } + inline void setTrackDisabledPen() { m_painter.setPen(m_trackDisabledPen); } inline void setTrackErasePen() { m_painter.setPen(m_trackErasePen); } - inline void setTurnoutStatePen() { m_painter.setPen(m_turnoutStatePen); } - inline QRectF turnoutStateRect(const QRectF& r) { return r.adjusted(m_turnoutMargin, m_turnoutMargin, -m_turnoutMargin, -m_turnoutMargin); } + inline void setTurnoutPen() { m_painter.setPen(m_turnoutDrawState ? m_trackPen : m_trackDisabledPen); } + inline void setTurnoutStatePen() { m_painter.setPen(m_turnoutDrawState ? m_turnoutStatePen : m_trackPen); } + inline QRectF turnoutStateRect(const QRectF& r) { return m_turnoutDrawState ? r.adjusted(m_turnoutMargin, m_turnoutMargin, -m_turnoutMargin, -m_turnoutMargin) : r; } QColor sensorStateToColor(SensorState value) const; void setBlockStateBrush(BlockState value); @@ -84,7 +80,7 @@ class TilePainter void drawRailBlock(const QRectF& r, TileRotate rotate, BlockState state = BlockState::Unknown, const std::vector subStates = {}); public: - TilePainter(QPainter& painter, int tileSize); + TilePainter(QPainter& painter, int tileSize, const BoardColorScheme& colorScheme); void draw(TileId id, const QRectF& r, TileRotate rotate); void drawSensor(TileId id, const QRectF& r, TileRotate rotate, SensorState state = SensorState::Unknown); diff --git a/client/src/settings/boardsettings.hpp b/client/src/settings/boardsettings.hpp index d3fdb71e..8fed84e6 100644 --- a/client/src/settings/boardsettings.hpp +++ b/client/src/settings/boardsettings.hpp @@ -25,12 +25,22 @@ #include "settingsbase.hpp" #include "setting.hpp" +#include class BoardSettings : public SettingsBase { + public: + enum class ColorScheme + { + Dark = 0, + Light = 1, + }; + private: BoardSettings() : SettingsBase("board") + , colorScheme{*this, "color_scheme", ColorScheme::Dark} + , turnoutDrawState{*this, "turnout_draw_state", true} , showBlockSensorStates{*this, "show_block_sensor_states", true} { } @@ -42,7 +52,23 @@ class BoardSettings : public SettingsBase return settings; } + Setting colorScheme; + Setting turnoutDrawState; Setting showBlockSensorStates; }; +ENUM_NAME(BoardSettings::ColorScheme, "board_settings.color_scheme") + +ENUM_VALUES(BoardSettings::ColorScheme, 2, +{ + {BoardSettings::ColorScheme::Dark, "dark"}, + {BoardSettings::ColorScheme::Light, "light"}, +}) + +template<> +struct SettingEnum +{ + static constexpr std::array values = {BoardSettings::ColorScheme::Dark, BoardSettings::ColorScheme::Light}; +}; + #endif diff --git a/client/src/settings/boardsettingswidget.cpp b/client/src/settings/boardsettingswidget.cpp index dab918e0..861d7287 100644 --- a/client/src/settings/boardsettingswidget.cpp +++ b/client/src/settings/boardsettingswidget.cpp @@ -28,6 +28,8 @@ BoardSettingsWidget::BoardSettingsWidget(QWidget* parent) { BoardSettings& s = BoardSettings::instance(); + addSetting(s.colorScheme); + addSetting(s.turnoutDrawState); addSetting(s.showBlockSensorStates); done(); diff --git a/client/src/settings/setting.hpp b/client/src/settings/setting.hpp index dafc3ac1..61169b6d 100644 --- a/client/src/settings/setting.hpp +++ b/client/src/settings/setting.hpp @@ -25,6 +25,12 @@ #include "settingsbase.hpp" +template +struct SettingEnum +{ + static_assert(sizeof(T) != sizeof(T)); +}; + template class Setting { diff --git a/client/src/settings/settingsbase.hpp b/client/src/settings/settingsbase.hpp index 6a90eb48..285140ff 100644 --- a/client/src/settings/settingsbase.hpp +++ b/client/src/settings/settingsbase.hpp @@ -39,16 +39,30 @@ class SettingsBase : public QObject template inline T get(const QString& key, const T& defaultValue) const { - return qvariant_cast(m_settings.value(key, defaultValue)); + if constexpr(std::is_enum_v) + return static_cast(m_settings.value(key, static_cast(defaultValue)).toUInt()); + else + return qvariant_cast(m_settings.value(key, defaultValue)); } template inline void set(const QString& key, const T& value) { - if(m_settings.value(key) != value) + if constexpr(std::is_enum_v) { - m_settings.setValue(key, value); - emit changed(); + if(m_settings.value(key) != static_cast(value)) + { + m_settings.setValue(key, static_cast(value)); + emit changed(); + } + } + else + { + if(m_settings.value(key) != value) + { + m_settings.setValue(key, value); + emit changed(); + } } } diff --git a/client/src/settings/settingsbasewidget.hpp b/client/src/settings/settingsbasewidget.hpp index a965a8eb..c30ebf76 100644 --- a/client/src/settings/settingsbasewidget.hpp +++ b/client/src/settings/settingsbasewidget.hpp @@ -24,7 +24,10 @@ #define TRAINTASTIC_CLIENT_SETTINGS_SETTINGSBASEWIDGET_HPP #include +#include +#include #include "setting.hpp" +#include "../utils/enum.hpp" class SettingsBaseWidget : public QScrollArea { @@ -40,11 +43,31 @@ class SettingsBaseWidget : public QScrollArea void addSettingOnOff(Setting& setting); void addSettingDir(Setting& setting); + template + void addSettingEnumDropdown(Setting& setting) + { + QComboBox* cb = new QComboBox(widget()); + for(auto value : SettingEnum::values) + { + cb->addItem(translateEnum(EnumName::value, static_cast(value)), static_cast(value)); + if(setting.value() == value) + cb->setCurrentIndex(cb->count() - 1); + } + connect(cb, QOverload::of(&QComboBox::currentIndexChanged), + [&setting, cb](int /*index*/) + { + setting.setValue(static_cast(cb->currentData().toUInt())); + }); + add(setting.name(), cb); + } + template inline void addSetting(Setting& setting) { if constexpr(std::is_same_v) addSettingOnOff(setting); + else if constexpr(std::is_enum_v) + addSettingEnumDropdown(setting); else static_assert(sizeof(T) != sizeof(T)); } diff --git a/client/src/utils/enum.cpp b/client/src/utils/enum.cpp index da4e3fb5..4abf622d 100644 --- a/client/src/utils/enum.cpp +++ b/client/src/utils/enum.cpp @@ -40,6 +40,7 @@ #include #include #include +#include "../settings/boardsettings.hpp" #define GET_ENUM_VALUES(_type) \ if(enumName == EnumName<_type>::value) \ @@ -83,6 +84,7 @@ QString translateEnum(const QString& enumName, qint64 value) TRANSLATE_ENUM(WorldScale) TRANSLATE_ENUM(XpressNetCommandStation) TRANSLATE_ENUM(XpressNetSerialInterface) + TRANSLATE_ENUM(BoardSettings::ColorScheme) return enumName + "@" + QString::number(value); } diff --git a/shared/translations/en-us.txt b/shared/translations/en-us.txt index 3fb91f0b..d0bc9b15 100644 --- a/shared/translations/en-us.txt +++ b/shared/translations/en-us.txt @@ -5,6 +5,9 @@ board:move_tile=Move tile board:resize_tile=Resize tile board:resize_to_contents=Resize to contents +board_settings.color_scheme:dark=Dark +board_settings.color_scheme:light=Light + board_tile.rail.block:input_map=Sensors board_tile.rail.sensor:input=Input @@ -333,7 +336,9 @@ qtapp.object_select_list_dialog:cancel=Cancel qtapp.object_select_list_dialog:ok=Ok qtapp.object_select_list_dialog:select_object=Select object +qtapp.settings.board:color_scheme=Color scheme qtapp.settings.board:show_block_sensor_states=Show block sensor states +qtapp.settings.board:turnout_draw_state=Draw turnout state qtapp.settings.developer:dont_load_fallback_language=Don't load fallback language qtapp.settings.developer:log_missing_strings=Log missing translations From ee9ad01e1ea3ba3f6b7df0961ccac52baca47189 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Fri, 15 Oct 2021 23:21:59 +0200 Subject: [PATCH 06/43] fixed warnings --- client/src/board/boardareawidget.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/board/boardareawidget.cpp b/client/src/board/boardareawidget.cpp index 16c82239..cce873a9 100644 --- a/client/src/board/boardareawidget.cpp +++ b/client/src/board/boardareawidget.cpp @@ -353,6 +353,9 @@ void BoardAreaWidget::mouseMoveEvent(QMouseEvent* event) std::max(1, 1 + std::max(old.y, tl.y) - m_mouseMoveHideTileLocation.y), tileSize)); break; + + case MouseMoveAction::None: + break; } } } @@ -534,6 +537,9 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event) } } break; + + case MouseMoveAction::None: + break; } } From a5d0654affaccefb7ef30675f3a5b5163863a32d Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 16 Oct 2021 10:03:27 +0200 Subject: [PATCH 07/43] fix: aded missing include (for windows) --- client/src/settings/boardsettings.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/settings/boardsettings.hpp b/client/src/settings/boardsettings.hpp index 8fed84e6..41e7a11f 100644 --- a/client/src/settings/boardsettings.hpp +++ b/client/src/settings/boardsettings.hpp @@ -23,6 +23,7 @@ #ifndef TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP #define TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP +#include #include "settingsbase.hpp" #include "setting.hpp" #include From e3e3551890771ed911e965fb49e0003a89cd7444 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 20 Oct 2021 09:29:28 +0200 Subject: [PATCH 08/43] fix: added missing rotate positions to rail cross 45 --- client/src/board/boardwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/board/boardwidget.cpp b/client/src/board/boardwidget.cpp index 8d7b106e..494ec348 100644 --- a/client/src/board/boardwidget.cpp +++ b/client/src/board/boardwidget.cpp @@ -57,7 +57,7 @@ const std::array tileInfo = { TileInfo{QStringLiteral("board_tile.rail.curve_45"), TileId::RailCurve45, 0xFF}, TileInfo{QStringLiteral("board_tile.rail.curve_90"), TileId::RailCurve90, 0xFF}, TileInfo{QStringLiteral(""), TileId::None, 0}, - TileInfo{QStringLiteral("board_tile.rail.cross_45"), TileId::RailCross45, 0x03}, + TileInfo{QStringLiteral("board_tile.rail.cross_45"), TileId::RailCross45, 0x0F}, TileInfo{QStringLiteral("board_tile.rail.cross_90"), TileId::RailCross90, 0x03}, TileInfo{QStringLiteral("board_tile.rail.bridge_45_left"), TileId::RailBridge45Left, 0x0F}, TileInfo{QStringLiteral("board_tile.rail.bridge_45_right"), TileId::RailBridge45Right, 0x0F}, From a0879534665fceb2cbfa048ccf7d34f19952268c Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 23 Oct 2021 00:03:28 +0200 Subject: [PATCH 09/43] fix: resize wasn't properly drawn for negative x / y --- client/src/board/boardareawidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/board/boardareawidget.cpp b/client/src/board/boardareawidget.cpp index cce873a9..82b366d7 100644 --- a/client/src/board/boardareawidget.cpp +++ b/client/src/board/boardareawidget.cpp @@ -347,8 +347,8 @@ void BoardAreaWidget::mouseMoveEvent(QMouseEvent* event) case MouseMoveAction::ResizeTile: update(updateTileRect( - m_mouseMoveHideTileLocation.x, - m_mouseMoveHideTileLocation.y, + m_mouseMoveHideTileLocation.x - originX, + m_mouseMoveHideTileLocation.y - originY, std::max(1, 1 + std::max(old.x, tl.x) - m_mouseMoveHideTileLocation.x), std::max(1, 1 + std::max(old.y, tl.y) - m_mouseMoveHideTileLocation.y), tileSize)); From 3088e9b866fad1f50b9990cb209f1678e42ea8b9 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 23 Oct 2021 00:40:37 +0200 Subject: [PATCH 10/43] fixed warnings --- client/src/board/boardwidget.cpp | 74 ++++++++++--------- client/src/dialog/objectselectlistdialog.cpp | 4 +- client/src/dialog/worldlistdialog.cpp | 4 +- client/src/mdiarea.cpp | 2 +- client/src/network/abstractproperty.hpp | 8 +- client/src/network/abstractvectorproperty.hpp | 12 +-- client/src/network/board.cpp | 2 +- client/src/network/board.hpp | 2 +- client/src/network/connection.cpp | 34 ++++----- client/src/network/outputmap.cpp | 4 +- client/src/network/serverlogtablemodel.cpp | 2 +- client/src/network/utils.cpp | 33 +++++++++ client/src/settings/generalsettingswidget.cpp | 3 +- client/src/widget/object/itemseditwidget.cpp | 3 +- client/src/widget/object/objecteditwidget.cpp | 2 +- .../widget/objectlist/objectlistwidget.cpp | 8 +- client/src/widget/propertycombobox.cpp | 10 +++ 17 files changed, 125 insertions(+), 82 deletions(-) diff --git a/client/src/board/boardwidget.cpp b/client/src/board/boardwidget.cpp index 494ec348..7db8b462 100644 --- a/client/src/board/boardwidget.cpp +++ b/client/src/board/boardwidget.cpp @@ -107,7 +107,7 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : connect(name, &AbstractProperty::valueChangedString, this, &BoardWidget::setWindowTitle); setWindowTitle(name->toString()); } - QMenu* m; + QMenu* menu; QVBoxLayout* l = new QVBoxLayout(); l->setMargin(0); @@ -134,23 +134,23 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : m_toolButtonGrid->setToolTip(Locale::tr("qtapp:grid")); m_toolButtonGrid->setPopupMode(QToolButton::MenuButtonPopup); connect(m_toolButtonGrid, &QToolButton::pressed, m_toolButtonGrid, &QToolButton::showMenu); - m = new QMenu(this); - m_actionGridNone = m->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"), + menu = new QMenu(this); + m_actionGridNone = menu->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"), [this]() { m_boardArea->setGrid(BoardAreaWidget::Grid::None); }); - m_actionGridDot = m->addAction(Theme::getIcon("grid_dot"), Locale::tr("qtapp:grid_dot"), + m_actionGridDot = menu->addAction(Theme::getIcon("grid_dot"), Locale::tr("qtapp:grid_dot"), [this]() { m_boardArea->setGrid(BoardAreaWidget::Grid::Dot); }); - m_actionGridLine = m->addAction(Theme::getIcon("grid_line"), Locale::tr("qtapp:grid_line"), + m_actionGridLine = menu->addAction(Theme::getIcon("grid_line"), Locale::tr("qtapp:grid_line"), [this]() { m_boardArea->setGrid(BoardAreaWidget::Grid::Line); }); - m_toolButtonGrid->setMenu(m); + m_toolButtonGrid->setMenu(menu); toolbar->addWidget(m_toolButtonGrid); // edit toolbar: @@ -190,10 +190,10 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : })); m_editActionDelete->setCheckable(true); m_editActionDelete->setData(-1); - if(auto* m = m_object->getMethod("delete_tile")) + if(auto* method = m_object->getMethod("delete_tile")) { - m_editActionDelete->setEnabled(m->getAttributeBool(AttributeName::Enabled, true)); - connect(m, &Method::attributeChanged, this, + m_editActionDelete->setEnabled(method->getAttributeBool(AttributeName::Enabled, true)); + connect(method, &Method::attributeChanged, this, [this](AttributeName name, const QVariant& value) { if(name == AttributeName::Enabled) @@ -234,10 +234,10 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : m_addActions.append(action); if(auto* tb = dynamic_cast(m_toolbarEdit->widgetForAction(action))) tb->setPopupMode(QToolButton::MenuButtonPopup); - QMenu* m = new QMenu(this); + QMenu* toolbuttonMenu = new QMenu(this); for(auto subAction : actions) { - m->addAction(subAction); + toolbuttonMenu->addAction(subAction); connect(subAction, &QAction::triggered, this, [this, action, subAction]() { @@ -251,7 +251,7 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : action->setIcon(actions[0]->icon()); action->setText(actions[0]->text()); action->setData(actions[0]->data()); - action->setMenu(m); + action->setMenu(toolbuttonMenu); action->setCheckable(true); connect(action, &QAction::triggered, this, [this, action]() @@ -269,13 +269,15 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : } } - if(auto* m = m_object->getMethod("add_tile")) + if(auto* method = m_object->getMethod("add_tile")) { - const bool v = m->getAttributeBool(AttributeName::Enabled, true); - for(QAction* act : m_addActions) - act->setEnabled(v); + { + const bool v = method->getAttributeBool(AttributeName::Enabled, true); + for(QAction* act : m_addActions) + act->setEnabled(v); + } - connect(m, &Method::attributeChanged, this, + connect(method, &Method::attributeChanged, this, [this](AttributeName name, const QVariant& value) { if(name == AttributeName::Enabled) @@ -301,10 +303,10 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : if(Q_LIKELY(m_object)) m_object->callMethod("resize_to_contents"); }); - if(auto* m = m_object->getMethod("resize_to_contents")) + if(auto* method = m_object->getMethod("resize_to_contents")) { - m_editActionResizeToContents->setEnabled(m->getAttributeBool(AttributeName::Enabled, true)); - connect(m, &Method::attributeChanged, this, + m_editActionResizeToContents->setEnabled(method->getAttributeBool(AttributeName::Enabled, true)); + connect(method, &Method::attributeChanged, this, [this](AttributeName name, const QVariant& value) { if(name == AttributeName::Enabled) @@ -408,18 +410,18 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) m_tileMoveY = y; m_tileMoveStarted = true; - const auto& data = m_object->tileData().at(l); + const auto& tileData = m_object->tileData().at(l); m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::MoveTile); - m_boardArea->setMouseMoveTileId(data.id()); - m_boardArea->setMouseMoveTileRotate(data.rotate()); - m_boardArea->setMouseMoveTileSize(data.width(), data.height()); + m_boardArea->setMouseMoveTileId(tileData.id()); + m_boardArea->setMouseMoveTileRotate(tileData.rotate()); + m_boardArea->setMouseMoveTileSize(tileData.width(), tileData.height()); m_boardArea->setMouseMoveHideTileLocation(l); } } else // drop { m_object->moveTile(m_tileMoveX, m_tileMoveY, x, y, false, - [this](const bool& r, Message::ErrorCode ec) + [this](const bool& /*r*/, Message::ErrorCode /*ec*/) { }); m_tileMoveStarted = false; @@ -449,10 +451,10 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) m_tileResizeY = l.y; m_tileResizeStarted = true; - const auto& data = m_object->tileData().at(l); + const auto& tileData = m_object->tileData().at(l); m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::ResizeTile); - m_boardArea->setMouseMoveTileId(data.id()); - m_boardArea->setMouseMoveTileRotate(data.rotate()); + m_boardArea->setMouseMoveTileId(tileData.id()); + m_boardArea->setMouseMoveTileRotate(tileData.rotate()); m_boardArea->setMouseMoveHideTileLocation(l); m_boardArea->setMouseMoveTileSizeMax(widthMax, heightMax); } @@ -465,7 +467,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) h >= 1 && h <= std::numeric_limits::max()) { m_object->resizeTile(m_tileResizeX, m_tileResizeY, w, h, - [this](const bool& r, Message::ErrorCode ec) + [this](const bool& /*r*/, Message::ErrorCode /*ec*/) { }); @@ -477,7 +479,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) else if(act == m_editActionDelete) { m_object->deleteTile(x, y, - [this](const bool& r, Message::ErrorCode ec) + [this](const bool& /*r*/, Message::ErrorCode /*ec*/) { }); } @@ -487,7 +489,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) const Qt::KeyboardModifiers kbMod = QApplication::keyboardModifiers(); if(kbMod == Qt::NoModifier || kbMod == Qt::ControlModifier) m_object->addTile(x, y, m_editRotate, classId, kbMod == Qt::ControlModifier, - [this](const bool& r, Message::ErrorCode ec) + [this](const bool& /*r*/, Message::ErrorCode /*ec*/) { }); } @@ -517,7 +519,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) void BoardWidget::rightClicked() { if(QAction* act = m_editActions->checkedAction()) - if(int index = act->data().toInt(); index >= 0 && Q_LIKELY(index < tileInfo.size())) + if(int index = act->data().toInt(); index >= 0 && Q_LIKELY(static_cast(index) < tileInfo.size())) { if(QApplication::keyboardModifiers() == Qt::NoModifier) m_editRotate += TileRotate::Deg45; @@ -528,18 +530,18 @@ void BoardWidget::rightClicked() } } -void BoardWidget::actionSelected(const TileInfo* tileInfo) +void BoardWidget::actionSelected(const TileInfo* info) { m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None); m_tileMoveStarted = false; m_tileResizeStarted = false; - if(tileInfo) + if(info) { - validRotate(m_editRotate, tileInfo->rotates); + validRotate(m_editRotate, info->rotates); m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::AddTile); m_boardArea->setMouseMoveTileRotate(m_editRotate); - m_boardArea->setMouseMoveTileId(tileInfo->id); + m_boardArea->setMouseMoveTileId(info->id); } } diff --git a/client/src/dialog/objectselectlistdialog.cpp b/client/src/dialog/objectselectlistdialog.cpp index 83684065..c796c76f 100644 --- a/client/src/dialog/objectselectlistdialog.cpp +++ b/client/src/dialog/objectselectlistdialog.cpp @@ -82,7 +82,7 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par m_object = object; m_requestId = m_item.object().connection()->getTableModel(m_object, - [this, spinner](const TableModelPtr& tableModel, Message::ErrorCode ec) + [this, spinner](const TableModelPtr& tableModel, Message::ErrorCode errorCode) { if(tableModel) { @@ -104,7 +104,7 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par delete spinner; } else - static_cast(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec))); + static_cast(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode))); }); } else diff --git a/client/src/dialog/worldlistdialog.cpp b/client/src/dialog/worldlistdialog.cpp index 13e25b8d..c6a6de63 100644 --- a/client/src/dialog/worldlistdialog.cpp +++ b/client/src/dialog/worldlistdialog.cpp @@ -72,7 +72,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr connection, QWidget m_object = object; m_requestId = m_connection->getTableModel(m_object, - [this, spinner](const TableModelPtr& tableModel, Message::ErrorCode ec) + [this, spinner](const TableModelPtr& tableModel, Message::ErrorCode errorCode) { if(tableModel) { @@ -95,7 +95,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr connection, QWidget delete spinner; } else - static_cast(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec))); + static_cast(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode))); }); } else diff --git a/client/src/mdiarea.cpp b/client/src/mdiarea.cpp index 9aae4104..c80bb8ef 100644 --- a/client/src/mdiarea.cpp +++ b/client/src/mdiarea.cpp @@ -46,7 +46,7 @@ void MdiArea::addBackgroundAction(QAction* action) void MdiArea::removeBackgroundAction(QAction* action) { - auto it = std::find_if(m_backgroundActionButtons.begin(), m_backgroundActionButtons.end(), [action](const auto& it) { return it.first == action; }); + auto it = std::find_if(m_backgroundActionButtons.begin(), m_backgroundActionButtons.end(), [action](const auto& i) { return i.first == action; }); if(it != m_backgroundActionButtons.end()) { delete it->second; diff --git a/client/src/network/abstractproperty.hpp b/client/src/network/abstractproperty.hpp index b43cdfc8..c2ce3cb4 100644 --- a/client/src/network/abstractproperty.hpp +++ b/client/src/network/abstractproperty.hpp @@ -72,10 +72,10 @@ class AbstractProperty : public BaseProperty return setValueInt64(static_cast(value)); } - [[nodiscard]] virtual int setValueBool(bool value, std::function callback) { Q_ASSERT(value != value); return -1; } - [[nodiscard]] virtual int setValueInt64(int64_t value, std::function callback) { Q_ASSERT(value != value); return -1; } - [[nodiscard]] virtual int setValueDouble(double value, std::function callback) { Q_ASSERT(value != value); return -1; } - [[nodiscard]] virtual int setValueString(const QString& value, std::function callback) { Q_ASSERT(value != value); return -1; } + [[nodiscard]] virtual int setValueBool(bool value, std::function /*callback*/) { Q_ASSERT(value != value); return -1; } + [[nodiscard]] virtual int setValueInt64(int64_t value, std::function /*callback*/) { Q_ASSERT(value != value); return -1; } + [[nodiscard]] virtual int setValueDouble(double value, std::function /*callback*/) { Q_ASSERT(value != value); return -1; } + [[nodiscard]] virtual int setValueString(const QString& value, std::function /*callback*/) { Q_ASSERT(value != value); return -1; } signals: void valueChanged(); diff --git a/client/src/network/abstractvectorproperty.hpp b/client/src/network/abstractvectorproperty.hpp index 4b2627f8..18fb7138 100644 --- a/client/src/network/abstractvectorproperty.hpp +++ b/client/src/network/abstractvectorproperty.hpp @@ -39,12 +39,12 @@ class AbstractVectorProperty : public BaseProperty public: virtual int size() const = 0; - virtual bool getBool(int index) const { Q_ASSERT(false); return false; } - virtual int getInt(int index) const { Q_ASSERT(false); return 0; } - virtual int64_t getInt64(int index) const { Q_ASSERT(false); return 0; } - virtual double getDouble(int index) const { Q_ASSERT(false); return 0; } - virtual QString getString(int index) const { Q_ASSERT(false); return ""; } - virtual QVariant getVariant(int index) const { Q_ASSERT(false); return QVariant(); } + virtual bool getBool(int /*index*/) const { Q_ASSERT(false); return false; } + virtual int getInt(int /*index*/) const { Q_ASSERT(false); return 0; } + virtual int64_t getInt64(int /*index*/) const { Q_ASSERT(false); return 0; } + virtual double getDouble(int /*index*/) const { Q_ASSERT(false); return 0; } + virtual QString getString(int /*index*/) const { Q_ASSERT(false); return ""; } + virtual QVariant getVariant(int /*index*/) const { Q_ASSERT(false); return QVariant(); } template T getEnum(int index) const diff --git a/client/src/network/board.cpp b/client/src/network/board.cpp index 275209f6..7e53d498 100644 --- a/client/src/network/board.cpp +++ b/client/src/network/board.cpp @@ -24,7 +24,7 @@ #include "connection.hpp" #include "callmethod.hpp" -Board::Board(std::shared_ptr connection, Handle handle, const QString& classId) : +Board::Board(std::shared_ptr connection, Handle handle) : Object(std::move(connection), handle, classId), m_getTileDataRequestId{Connection::invalidRequestId} { diff --git a/client/src/network/board.hpp b/client/src/network/board.hpp index 103efc83..65182bb8 100644 --- a/client/src/network/board.hpp +++ b/client/src/network/board.hpp @@ -56,7 +56,7 @@ class Board final : public Object public: inline static const QString classId = QStringLiteral("board"); - Board(std::shared_ptr connection, Handle handle, const QString& classId); + Board(std::shared_ptr connection, Handle handle); ~Board() final; void getTileData(); diff --git a/client/src/network/connection.cpp b/client/src/network/connection.cpp index 0bbe2b67..f7198dd6 100644 --- a/client/src/network/connection.cpp +++ b/client/src/network/connection.cpp @@ -483,7 +483,7 @@ ObjectPtr Connection::readObject(const Message& message) else if(classId.startsWith(OutputMap::classIdPrefix)) p = new OutputMap(shared_from_this(), handle, classId); else if(classId == Board::classId) - p = new Board(shared_from_this(), handle, classId); + p = new Board(shared_from_this(), handle); else p = new Object(shared_from_this(), handle, classId); @@ -593,14 +593,14 @@ ObjectPtr Connection::readObject(const Message& message) { message.readBlock(); // item const AttributeName attributeName = message.read(); - const ValueType type = message.read(); + const ValueType valueType = message.read(); switch(message.read()) { case AttributeType::Value: { QVariant value; - switch(type) + switch(valueType) { case ValueType::Boolean: value = message.read(); @@ -632,7 +632,7 @@ ObjectPtr Connection::readObject(const Message& message) case AttributeType::Values: { const int length = message.read(); // read uint32_t as int, Qt uses int for length - QList values = readArray(message, type, length); + QList values = readArray(message, valueType, length); if(Q_LIKELY(values.length() == length)) item->m_attributes[attributeName] = values; break; @@ -683,7 +683,7 @@ void Connection::getWorld() setWorld(nullptr); else m_worldRequestId = getObject(m_worldProperty->objectId(), - [this](const ObjectPtr& object, Message::ErrorCode ec) + [this](const ObjectPtr& object, Message::ErrorCode /*ec*/) { m_worldRequestId = invalidRequestId; setWorld(object); @@ -1039,22 +1039,22 @@ void Connection::processMessage(const std::shared_ptr message) void Connection::socketConnected() { - std::unique_ptr request{Message::newRequest(Message::Command::Login)}; - request->write(m_username.toUtf8()); - request->write(m_password); - send(request, - [this](const std::shared_ptr message) + std::unique_ptr loginRequest{Message::newRequest(Message::Command::Login)}; + loginRequest->write(m_username.toUtf8()); + loginRequest->write(m_password); + send(loginRequest, + [this](const std::shared_ptr loginResponse) { - if(message && message->isResponse() && !message->isError()) + if(loginResponse && loginResponse->isResponse() && !loginResponse->isError()) { - std::unique_ptr request{Message::newRequest(Message::Command::NewSession)}; - send(request, - [this](const std::shared_ptr message) + std::unique_ptr newSessionRequest{Message::newRequest(Message::Command::NewSession)}; + send(newSessionRequest, + [this](const std::shared_ptr newSessionResonse) { - if(message && message->isResponse() && !message->isError()) + if(newSessionResonse && newSessionResonse->isResponse() && !newSessionResonse->isError()) { - message->read(m_sessionUUID); - m_traintastic = readObject(*message); + newSessionResonse->read(m_sessionUUID); + m_traintastic = readObject(*newSessionResonse); m_worldProperty = dynamic_cast(m_traintastic->getProperty("world")); connect(m_worldProperty, &ObjectProperty::valueChanged, this, [this]() diff --git a/client/src/network/outputmap.cpp b/client/src/network/outputmap.cpp index 0f155e10..531162b5 100644 --- a/client/src/network/outputmap.cpp +++ b/client/src/network/outputmap.cpp @@ -51,11 +51,11 @@ void OutputMap::getItems() { m_getItemsRequestId = Connection::invalidRequestId; - if(auto c = connection()) + if(auto con = connection()) { Items objects; while(!response->endOfMessage()) - objects.emplace_back(c->readObject(*response)); + objects.emplace_back(con->readObject(*response)); m_items = std::move(objects); emit itemsChanged(); diff --git a/client/src/network/serverlogtablemodel.cpp b/client/src/network/serverlogtablemodel.cpp index 5d01cc90..6368377b 100644 --- a/client/src/network/serverlogtablemodel.cpp +++ b/client/src/network/serverlogtablemodel.cpp @@ -122,7 +122,7 @@ void ServerLogTableModel::processMessage(const Message& message) log.code = message.read(); log.message = Locale::tr("message:" + toCodeString(log.code)); const int argc = message.read(); - for(int i = 0; i < argc; i++) + for(int j = 0; j < argc; j++) log.message = log.message.arg(QString::fromUtf8(message.read())); m_logs.append(log); } diff --git a/client/src/network/utils.cpp b/client/src/network/utils.cpp index edcfd2d9..328b3725 100644 --- a/client/src/network/utils.cpp +++ b/client/src/network/utils.cpp @@ -31,10 +31,43 @@ QString errorCodeToText(Message::ErrorCode ec) QString text; switch(ec) { + case ErrorCode::InvalidCommand: + text = "Invalid command"; + break; + + case ErrorCode::Failed: + text = "Failed"; + break; + + case ErrorCode::AuthenticationFailed: + text = "Authentication failed"; + break; + + case ErrorCode::InvalidSession: + text = "Invalid session"; + break; + case ErrorCode::UnknownObject: text = "Unknown object"; break; + case ErrorCode::ObjectNotTable: + text = "Object not table"; + break; + + case ErrorCode::UnknownClassId: + text = "Unknown class id"; + break; + + case ErrorCode::Other: + assert(false); + text = "Other"; + break; + + case ErrorCode::Unknown: + text = "Unknown"; + break; + case ErrorCode::None: break; // silence warning } diff --git a/client/src/settings/generalsettingswidget.cpp b/client/src/settings/generalsettingswidget.cpp index 060ca3e3..1c63f74c 100644 --- a/client/src/settings/generalsettingswidget.cpp +++ b/client/src/settings/generalsettingswidget.cpp @@ -66,9 +66,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(QWidget* parent) { GeneralSettings::instance().language.setValue(cb->itemData(index).toString()); }); - add(GeneralSettings::instance().language.name(), cb); + add(s.language.name(), cb); } - done(); } diff --git a/client/src/widget/object/itemseditwidget.cpp b/client/src/widget/object/itemseditwidget.cpp index 51748359..737a04b0 100644 --- a/client/src/widget/object/itemseditwidget.cpp +++ b/client/src/widget/object/itemseditwidget.cpp @@ -82,7 +82,6 @@ void ItemsEditWidget::buildForm() QVBoxLayout* left = new QVBoxLayout(); QToolBar* toolbar = new QToolBar(this); - QAction* act; if(m_methodAdd) toolbar->addAction(new MethodAction(Theme::getIcon("add"), *m_methodAdd, toolbar)); @@ -133,7 +132,7 @@ void ItemsEditWidget::buildForm() connect(m_propertyItems, &ObjectVectorProperty::valueChanged, this, &ItemsEditWidget::itemsChanged); itemsChanged(); connect(m_list, &QListWidget::currentItemChanged, this, - [this](QListWidgetItem* current, QListWidgetItem* previous) + [this](QListWidgetItem* current, QListWidgetItem* /*previous*/) { if(current) if(auto* w = m_items.value(current->data(objectIdRole).toString())) diff --git a/client/src/widget/object/objecteditwidget.cpp b/client/src/widget/object/objecteditwidget.cpp index 70d5558a..5fdaec42 100644 --- a/client/src/widget/object/objecteditwidget.cpp +++ b/client/src/widget/object/objecteditwidget.cpp @@ -96,7 +96,7 @@ void ObjectEditWidget::buildForm() ObjectProperty* property = static_cast(baseProperty); if(contains(baseProperty->flags(), PropertyFlags::SubObject)) { - QWidget* w = new ObjectEditWidget(property->objectId()); + w = new ObjectEditWidget(property->objectId()); w->setWindowTitle(property->displayName()); tabs.append(w); continue; diff --git a/client/src/widget/objectlist/objectlistwidget.cpp b/client/src/widget/objectlist/objectlistwidget.cpp index caec0d98..16e302d3 100644 --- a/client/src/widget/objectlist/objectlistwidget.cpp +++ b/client/src/widget/objectlist/objectlistwidget.cpp @@ -49,9 +49,9 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) : QWidget(parent), - m_buttonAdd{nullptr}, m_object{object}, m_toolbar{new QToolBar()}, + m_buttonAdd{nullptr}, m_actionAdd{nullptr}, m_actionEdit{nullptr}, m_actionDelete{nullptr}, @@ -102,12 +102,12 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) : m_object->connection()->cancelRequest(m_requestIdAdd); m_requestIdAdd = method->call( - [this](const ObjectPtr& object, Message::ErrorCode /*ec*/) + [this](const ObjectPtr& addedObject, Message::ErrorCode /*ec*/) { m_requestIdAdd = Connection::invalidRequestId; - if(object) + if(addedObject) { - MainWindow::instance->showObject(object); + MainWindow::instance->showObject(addedObject); } // TODO: show error }); diff --git a/client/src/widget/propertycombobox.cpp b/client/src/widget/propertycombobox.cpp index ecbb1f03..3cc6313d 100644 --- a/client/src/widget/propertycombobox.cpp +++ b/client/src/widget/propertycombobox.cpp @@ -21,6 +21,7 @@ */ #include "propertycombobox.hpp" +#include #include "../network/property.hpp" #include "../utils/internalupdateholder.hpp" #include "../utils/enum.hpp" @@ -103,6 +104,15 @@ void PropertyComboBox::updateValues() setCurrentIndex(count() - 1); } break; + + case ValueType::Invalid: + case ValueType::Boolean: + case ValueType::Float: + case ValueType::String: + case ValueType::Object: + case ValueType::Set: + assert(false); + break; } } } From 1aa8f55969b147967a32b49f4a8884fb90510671 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 7 Nov 2021 16:05:18 +0100 Subject: [PATCH 11/43] board: added two zoom levels --- client/src/board/boardareawidget.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/board/boardareawidget.hpp b/client/src/board/boardareawidget.hpp index 5042ddd1..402e6fbc 100644 --- a/client/src/board/boardareawidget.hpp +++ b/client/src/board/boardareawidget.hpp @@ -113,7 +113,7 @@ class BoardAreaWidget : public QWidget void updateMinimumSize(); public: - static constexpr int zoomLevelMin = 0; + static constexpr int zoomLevelMin = -2; static constexpr int zoomLevelMax = 15; BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr); From c52164dad0b61eab87f9b0816b7305b709bbf262 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 7 Nov 2021 16:28:07 +0100 Subject: [PATCH 12/43] board: added zoom percentage to status bar --- client/src/board/boardareawidget.hpp | 4 +++- client/src/board/boardwidget.cpp | 3 +++ client/src/board/boardwidget.hpp | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/board/boardareawidget.hpp b/client/src/board/boardareawidget.hpp index 402e6fbc..f1f9b527 100644 --- a/client/src/board/boardareawidget.hpp +++ b/client/src/board/boardareawidget.hpp @@ -92,7 +92,8 @@ class BoardAreaWidget : public QWidget inline int boardRight() const { return Q_LIKELY(m_boardRight) ? m_boardRight->toInt() + boardMargin: 0; } inline int boardBottom() const { return Q_LIKELY(m_boardBottom) ? m_boardBottom->toInt() + boardMargin: 0; } - int getTileSize() const { return 25 + m_zoomLevel * 5; } + static constexpr int getTileSize(int zoomLevel) { return 25 + zoomLevel * 5; } + int getTileSize() const { return getTileSize(m_zoomLevel); } TurnoutPosition getTurnoutPosition(const TileLocation& l) const; BlockState getBlockState(const TileLocation& l) const; std::vector getBlockSensorStates(const TileLocation& l) const; @@ -121,6 +122,7 @@ class BoardAreaWidget : public QWidget Grid grid() const { return m_grid; } void nextGrid(); int zoomLevel() const { return m_zoomLevel; } + float zoomRatio() const { return static_cast(getTileSize()) / getTileSize(0); } void setMouseMoveAction(MouseMoveAction action); void setMouseMoveTileId(TileId id); diff --git a/client/src/board/boardwidget.cpp b/client/src/board/boardwidget.cpp index 7db8b462..b9fccb05 100644 --- a/client/src/board/boardwidget.cpp +++ b/client/src/board/boardwidget.cpp @@ -97,6 +97,7 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : m_statusBar{new QStatusBar(this)}, m_statusBarMessage{new QLabel(this)}, m_statusBarCoords{new QLabel(this)}, + m_statusBarZoom{new QLabel(this)}, m_editActions{new QActionGroup(this)}, m_editRotate{TileRotate::Deg0} , m_tileMoveStarted{false} @@ -325,6 +326,7 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : m_statusBar->addWidget(m_statusBarMessage, 1); m_statusBar->addWidget(m_statusBarCoords, 0); + m_statusBar->addWidget(m_statusBarZoom, 0); l->addWidget(m_statusBar); AbstractProperty* edit = m_object->connection()->world()->getProperty("edit"); @@ -384,6 +386,7 @@ void BoardWidget::zoomLevelChanged(int value) { m_actionZoomIn->setEnabled(value < BoardAreaWidget::zoomLevelMax); m_actionZoomOut->setEnabled(value > BoardAreaWidget::zoomLevelMin); + m_statusBarZoom->setText(QString::number(100 * m_boardArea->zoomRatio(), 'f', 0) + " %"); } void BoardWidget::tileClicked(int16_t x, int16_t y) diff --git a/client/src/board/boardwidget.hpp b/client/src/board/boardwidget.hpp index e0906510..40f5a2cb 100644 --- a/client/src/board/boardwidget.hpp +++ b/client/src/board/boardwidget.hpp @@ -46,6 +46,7 @@ class BoardWidget : public QWidget QStatusBar* m_statusBar; QLabel* m_statusBarMessage; QLabel* m_statusBarCoords; + QLabel* m_statusBarZoom; QAction* m_actionZoomIn; QAction* m_actionZoomOut; QToolButton* m_toolButtonGrid; From 2dc42015dc4a37ec30d7791f19dbbfc67b1334f5 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 21:55:26 +0100 Subject: [PATCH 13/43] lua: added support for reading enum properties --- server/src/lua/enum.hpp | 15 ++++++++++----- server/src/lua/object.cpp | 7 ++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/server/src/lua/enum.hpp b/server/src/lua/enum.hpp index 8116a88f..29c2d410 100644 --- a/server/src/lua/enum.hpp +++ b/server/src/lua/enum.hpp @@ -32,6 +32,15 @@ namespace Lua { +inline void pushEnum(lua_State* L, const char* enumName, lua_Integer value) +{ + lua_getglobal(L, enumName); // get tabel with all enum values: key=int, value=userdata enum + assert(lua_istable(L, -1)); // check if enum is registered + lua_rawgeti(L, -1, value); // get userdata by key + lua_insert(L, lua_gettop(L) - 1); // swap table and userdata + lua_pop(L, 1); // remove table +} + template struct Enum { @@ -53,11 +62,7 @@ struct Enum static void push(lua_State* L, T value) { - lua_getglobal(L, EnumName::value); // get tabel with all enum values: key=int, value=userdata enum - assert(lua_istable(L, -1)); // check if enum is registered - lua_rawgeti(L, -1, static_cast(value)); // get userdata by key - lua_insert(L, lua_gettop(L) - 1); // swap table and userdata - lua_pop(L, 1); // remove table + pushEnum(L, EnumName::value, static_cast(value)); } static int __tostring(lua_State* L) diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index 2ac03be3..fd44d7c9 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -118,7 +118,12 @@ int Object::__index(lua_State* L) Lua::push(L, property->toBool()); break; - //case ValueType::Enum: + case ValueType::Enum: + // EnumName::value assigned to the std::string_view is NUL terminated, + // so it can be used as const char* however it is a bit tricky :) + assert(*(property->enumName().data() + property->enumName().size()) == '\0'); + pushEnum(L, property->enumName().data(), static_cast(property->toInt64())); + break; case ValueType::Integer: Lua::push(L, property->toInt64()); From 0546ef1c3039e48b7b4f56c41fde4b1db72190ee Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 21:58:09 +0100 Subject: [PATCH 14/43] lua: added WorldScale enum to sandbox --- server/src/lua/sandbox.cpp | 3 +++ server/test/lua/enum.cpp | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index d52b1f4b..99b3c7c6 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -32,6 +32,7 @@ #include "../enum/decoderprotocol.hpp" #include "../enum/direction.hpp" #include "../enum/worldevent.hpp" +#include "../enum/worldscale.hpp" #include "../set/worldstate.hpp" #define LUA_SANDBOX "_sandbox" @@ -65,6 +66,7 @@ SandboxPtr Sandbox::create(Script& script) Enum::registerType(L); Enum::registerType(L); Enum::registerType(L); + Enum::registerType(L); Set::registerType(L); Object::registerType(L); Method::registerType(L); @@ -109,6 +111,7 @@ SandboxPtr Sandbox::create(Script& script) Enum::registerValues(L); Enum::registerValues(L); Enum::registerValues(L); + Enum::registerValues(L); ReadOnlyTable::wrap(L, -1); lua_setfield(L, -2, "enum"); diff --git a/server/test/lua/enum.cpp b/server/test/lua/enum.cpp index f18062cb..7c35fbb4 100644 --- a/server/test/lua/enum.cpp +++ b/server/test/lua/enum.cpp @@ -31,6 +31,7 @@ #include "../../src/enum/decoderprotocol.hpp" #include "../../src/enum/direction.hpp" #include "../../src/enum/worldevent.hpp" +#include "../../src/enum/worldscale.hpp" template static lua_State* createState() @@ -46,7 +47,12 @@ static lua_State* createState() return L; } -TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", DecoderProtocol, Direction, WorldEvent) +TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]" + , DecoderProtocol + , Direction + , WorldEvent + , WorldScale + ) { const TestType firstKey = EnumValues::value.begin()->first; const TestType lastKey = EnumValues::value.rbegin()->first; From f0eac15247ad8f586fbbc63a3098fd614a1aa6ca Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 22:03:03 +0100 Subject: [PATCH 15/43] added property flags to control property access within the Lua scripting engine --- server/src/core/baseproperty.hpp | 12 ++++ server/src/lua/object.cpp | 59 ++++++++++--------- shared/src/traintastic/enum/propertyflags.hpp | 45 +++++--------- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/server/src/core/baseproperty.hpp b/server/src/core/baseproperty.hpp index 6538166e..a4028191 100644 --- a/server/src/core/baseproperty.hpp +++ b/server/src/core/baseproperty.hpp @@ -43,6 +43,7 @@ class BaseProperty : public InterfaceItem assert(type != ValueType::Invalid); assert(is_access_valid(flags)); assert(is_store_valid(flags)); + assert(isScriptValid(flags)); } void changed(); @@ -63,6 +64,17 @@ class BaseProperty : public InterfaceItem return (m_flags & PropertyFlagsStoreMask) == PropertyFlags::StoreState; } + bool isScriptReadable() const + { + const PropertyFlags scriptFlags = m_flags & PropertyFlagsScriptMask; + return scriptFlags == PropertyFlags::ScriptReadOnly || scriptFlags == PropertyFlags::ScriptReadWrite; + } + + bool isScriptWriteable() const + { + return (m_flags & PropertyFlagsScriptMask) == PropertyFlags::ScriptReadWrite; + } + PropertyFlags flags() const { return m_flags; diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index fd44d7c9..d1a41876 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -108,44 +108,47 @@ int Object::__index(lua_State* L) if(InterfaceItem* item = object->getItem(name)) { - // TODO: test scriptable - if(AbstractProperty* property = dynamic_cast(item)) { - switch(property->type()) + if(property->isScriptReadable()) { - case ValueType::Boolean: - Lua::push(L, property->toBool()); - break; + switch(property->type()) + { + case ValueType::Boolean: + Lua::push(L, property->toBool()); + break; - case ValueType::Enum: - // EnumName::value assigned to the std::string_view is NUL terminated, - // so it can be used as const char* however it is a bit tricky :) - assert(*(property->enumName().data() + property->enumName().size()) == '\0'); - pushEnum(L, property->enumName().data(), static_cast(property->toInt64())); - break; + case ValueType::Enum: + // EnumName::value assigned to the std::string_view is NUL terminated, + // so it can be used as const char* however it is a bit tricky :) + assert(*(property->enumName().data() + property->enumName().size()) == '\0'); + pushEnum(L, property->enumName().data(), static_cast(property->toInt64())); + break; - case ValueType::Integer: - Lua::push(L, property->toInt64()); - break; + case ValueType::Integer: + Lua::push(L, property->toInt64()); + break; - case ValueType::Float: - Lua::push(L, property->toDouble()); - break; + case ValueType::Float: + Lua::push(L, property->toDouble()); + break; - case ValueType::String: - Lua::push(L, property->toString()); - break; + case ValueType::String: + Lua::push(L, property->toString()); + break; - case ValueType::Object: - push(L, property->toObject()); - break; + case ValueType::Object: + push(L, property->toObject()); + break; - default: - assert(false); - lua_pushnil(L); - break; + default: + assert(false); + lua_pushnil(L); + break; + } } + else + lua_pushnil(L); } else if(AbstractMethod* method = dynamic_cast(item)) { diff --git a/shared/src/traintastic/enum/propertyflags.hpp b/shared/src/traintastic/enum/propertyflags.hpp index af9885ac..af972d9a 100644 --- a/shared/src/traintastic/enum/propertyflags.hpp +++ b/shared/src/traintastic/enum/propertyflags.hpp @@ -31,41 +31,18 @@ enum class PropertyFlags : uint16_t ReadOnly = 1 << 0, ReadWrite = PropertyFlags::ReadOnly | 2 << 0, - // bit 2..4 + // bit 2..3 NoStore = 1 << 2, Store = 2 << 2, StoreState = 3 << 2, - // bit 5 - SubObject = 1 << 4 + // bit 4 + SubObject = 1 << 4, -/* - - // Access: - EditConstant = 1 << 0, - EditReadOnly = 2 << 0, - EditReadWrite = 3 << 0, - StopConstant = 1 << 2, - StopReadOnly = 2 << 2, - StopReadWrite = 3 << 2, - RunConstant = 1 << 4, - RunReadOnly = 2 << 4, - RunReadWrite = 3 << 4, - - AccessRRR = PropertyFlags::EditReadOnly | PropertyFlags::StopReadOnly | PropertyFlags::RunReadOnly, - AccessRRW = PropertyFlags::EditReadOnly | PropertyFlags::StopReadOnly | PropertyFlags::RunReadWrite, - AccessRWW = PropertyFlags::EditReadOnly | PropertyFlags::StopReadWrite | PropertyFlags::RunReadWrite, - AccessWCC = PropertyFlags::EditReadWrite | PropertyFlags::StopConstant | PropertyFlags::RunConstant, - AccessWWW = PropertyFlags::EditReadWrite | PropertyFlags::StopReadWrite | PropertyFlags::RunReadWrite, - // Store: - NoStore = 1 << 6, - Store = 2 << 6, - StoreState = 3 << 6, - - - - TODO = AccessWWW, - */ + // bit 5..6 + NoScript = 1 << 5, + ScriptReadOnly = 2 << 5, + ScriptReadWrite = 3 << 5, }; constexpr PropertyFlags operator| (const PropertyFlags& lhs, const PropertyFlags& rhs) @@ -100,6 +77,7 @@ constexpr bool is_empty(const PropertyFlags value) constexpr PropertyFlags PropertyFlagsAccessMask = static_cast(0x0003); constexpr PropertyFlags PropertyFlagsStoreMask = static_cast(0x000C); +constexpr PropertyFlags PropertyFlagsScriptMask = static_cast(0x0060); constexpr bool is_access_valid(const PropertyFlags value) { @@ -113,4 +91,11 @@ constexpr bool is_store_valid(const PropertyFlags /*value*/) //return (store == PropertyFlags::NoStore) || (store == PropertyFlags::Store) || (store == PropertyFlags::StoreState); } +constexpr bool isScriptValid(const PropertyFlags /*value*/) +{ + return true; + //const PropertyFlags script = value & PropertyFlagsScriptMask; + //return (script == PropertyFlags::NoScript) || (script == PropertyFlags::ScriptReadOnly) || (script == PropertyFlags::ScriptReadWrite); +} + #endif From 274537038ab706219ca5e6dc403dfa1c0d34d075 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 22:42:18 +0100 Subject: [PATCH 16/43] lua: added support for reading set properties changed lua set storage from T to lua_Integer --- server/src/lua/object.cpp | 7 +++++++ server/src/lua/set.hpp | 37 +++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index d1a41876..5e534001 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -141,6 +141,13 @@ int Object::__index(lua_State* L) push(L, property->toObject()); break; + case ValueType::Set: + // set_name::value assigned to the std::string_view is NUL terminated, + // so it can be used as const char* however it is a bit tricky :) + assert(*(property->setName().data() + property->setName().size()) == '\0'); + pushSet(L, property->setName().data(), static_cast(property->toInt64())); + break; + default: assert(false); lua_pushnil(L); diff --git a/server/src/lua/set.hpp b/server/src/lua/set.hpp index 7c49b42a..aacaccd2 100644 --- a/server/src/lua/set.hpp +++ b/server/src/lua/set.hpp @@ -49,6 +49,23 @@ struct set_values template constexpr auto set_values_v = set_values::value; +inline void pushSet(lua_State* L, const char* setName, lua_Integer value) +{ + lua_getglobal(L, setName); // get tabel with all values: key=int, value=set as userdata + assert(lua_type(L, -1) == LUA_TTABLE); + lua_rawgeti(L, -1, value); // get userdata + if(lua_isnil(L, -1)) // value not in table + { + lua_pop(L, 1); // remove nil + *static_cast(lua_newuserdata(L, sizeof(value))) = value; + luaL_setmetatable(L, setName); + lua_pushvalue(L, -1); // copy set userdata on stack + lua_rawseti(L, -3, value); // add set value to table + } + lua_insert(L, lua_gettop(L) - 1); // swap tabel and userdata + lua_pop(L, 1); // remove tabel +} + template struct Set { @@ -57,32 +74,20 @@ struct Set static T check(lua_State* L, int index) { - return *static_cast(luaL_checkudata(L, index, set_name_v)); + return static_cast(*static_cast(luaL_checkudata(L, index, set_name_v))); } static bool test(lua_State* L, int index, T& value) { - T* data = static_cast(luaL_testudata(L, index, set_name_v)); + lua_Integer* data = static_cast(luaL_testudata(L, index, set_name_v)); if(data) - value = *data; + value = static_cast(*data); return data; } static void push(lua_State* L, T value) { - lua_getglobal(L, set_name_v); // get tabel with all values: key=int, value=set as userdata - assert(lua_type(L, -1) == LUA_TTABLE); - lua_rawgeti(L, -1, static_cast(value)); // get userdata - if(lua_isnil(L, -1)) // value not in table - { - lua_pop(L, 1); // remove nil - *static_cast(lua_newuserdata(L, sizeof(value))) = value; - luaL_setmetatable(L, set_name_v); - lua_pushvalue(L, -1); // copy set userdata on stack - lua_rawseti(L, -3, static_cast(value)); // add set value to table - } - lua_insert(L, lua_gettop(L) - 1); // swap tabel and userdata - lua_pop(L, 1); // remove tabel + pushSet(L, set_name_v, static_cast(value)); } static int __add(lua_State* L) From b9897c996d6f672efdf525a08f771f9f9b9cc3ba Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 22:43:50 +0100 Subject: [PATCH 17/43] lua: enabled readonly access to world uuid/name/scale/scale_ratio and state properties --- server/src/world/world.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index 97748a78..ad4bba50 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -65,10 +65,10 @@ void World::init(const std::shared_ptr& world) World::World(Private) : Object(), - uuid{this, "uuid", to_string(boost::uuids::random_generator()()), PropertyFlags::ReadOnly | PropertyFlags::NoStore}, - name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, - scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store, [this](WorldScale /*value*/){ updateScaleRatio(); }}, - scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store}, + uuid{this, "uuid", to_string(boost::uuids::random_generator()()), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}, + name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, + scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](WorldScale /*value*/){ updateScaleRatio(); }}, + scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, commandStations{this, "command_stations", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, @@ -83,7 +83,7 @@ World::World(Private) : #ifndef DISABLE_LUA_SCRIPTING luaScripts{this, "lua_scripts", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, #endif - state{this, "state", WorldState(), PropertyFlags::ReadOnly | PropertyFlags::NoStore}, + state{this, "state", WorldState(), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}, edit{this, "edit", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, [this](bool value) { From 803ca40ec6f5d918769e10bf477207f2d4fb54c1 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 23:19:49 +0100 Subject: [PATCH 18/43] added method flags to control method access within the Lua scripting engine --- server/src/core/abstractmethod.cpp | 7 +++--- server/src/core/abstractmethod.hpp | 12 ++++++++-- server/src/core/method.hpp | 6 +++++ server/src/core/methodflags.hpp | 36 ++++++++++++++++++++++++++++++ server/src/lua/object.cpp | 5 ++++- 5 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 server/src/core/methodflags.hpp diff --git a/server/src/core/abstractmethod.cpp b/server/src/core/abstractmethod.cpp index 5079bae3..f5cb4ee1 100644 --- a/server/src/core/abstractmethod.cpp +++ b/server/src/core/abstractmethod.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -22,7 +22,8 @@ #include "abstractmethod.hpp" -AbstractMethod::AbstractMethod(Object& object, const std::string& name) : - InterfaceItem(object, name) +AbstractMethod::AbstractMethod(Object& object, const std::string& name, MethodFlags flags) + : InterfaceItem(object, name) + , m_flags{flags} { } diff --git a/server/src/core/abstractmethod.hpp b/server/src/core/abstractmethod.hpp index 04ab02b4..eb31b004 100644 --- a/server/src/core/abstractmethod.hpp +++ b/server/src/core/abstractmethod.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,6 +24,7 @@ #define TRAINTASTIC_SERVER_CORE_ABSTRACTMETHOD_HPP #include "interfaceitem.hpp" +#include "methodflags.hpp" #include #include #include @@ -31,6 +32,9 @@ class AbstractMethod : public InterfaceItem { + private: + const MethodFlags m_flags; + public: class MethodCallError : public std::runtime_error { @@ -94,7 +98,11 @@ class AbstractMethod : public InterfaceItem using Argument = std::variant; using Result = std::variant; - AbstractMethod(Object& object, const std::string& name); + AbstractMethod(Object& object, const std::string& name, MethodFlags m_flags = noMethodFlags); + + inline bool isScriptCallable() const { return m_flags == MethodFlags::ScriptCallable; } + + inline MethodFlags flags() const { return m_flags; } virtual std::size_t argumentCount() const = 0; virtual std::vector argumentTypes() const = 0; diff --git a/server/src/core/method.hpp b/server/src/core/method.hpp index f2c50205..50891185 100644 --- a/server/src/core/method.hpp +++ b/server/src/core/method.hpp @@ -96,6 +96,12 @@ class Method : public AbstractMethod std::function m_function; public: + Method(Object& object, const std::string& name, MethodFlags flags, std::function function) : + AbstractMethod(object, name, flags), + m_function{std::move(function)} + { + } + Method(Object& object, const std::string& name, std::function function) : AbstractMethod(object, name), m_function{std::move(function)} diff --git a/server/src/core/methodflags.hpp b/server/src/core/methodflags.hpp new file mode 100644 index 00000000..80402bb6 --- /dev/null +++ b/server/src/core/methodflags.hpp @@ -0,0 +1,36 @@ +/** + * server/src/core/methodflags.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_CORE_METHODFLAGS_HPP +#define TRAINTASTIC_SERVER_CORE_METHODFLAGS_HPP + +enum class MethodFlags +{ + // bit 0..1 + NoScript = 1 << 0, + ScriptCallable = 2 << 0, +}; + +/// temporary placeholder, should be removed in the future when all method have their flags set +constexpr MethodFlags noMethodFlags = static_cast(0); + +#endif diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index 5e534001..4f33ce0c 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -159,7 +159,10 @@ int Object::__index(lua_State* L) } else if(AbstractMethod* method = dynamic_cast(item)) { - Method::push(L, *method); + if(method->isScriptCallable()) + Method::push(L, *method); + else + lua_pushnil(L); } else { From 947e9bb1aab3cedc74c369a15b073cd52cf1b63a Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 23:48:30 +0100 Subject: [PATCH 19/43] lua: fixed VERSION constants --- server/src/lua/sandbox.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index 99b3c7c6..fc0f4739 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -26,6 +26,7 @@ #include "method.hpp" #include "log.hpp" #include "class.hpp" +#include #include #include #include "../world/world.hpp" @@ -81,8 +82,14 @@ SandboxPtr Sandbox::create(Script& script) ADD_GLOBAL_TO_SANDBOX("ipairs") // set VERSION: - lua_pushstring(L, STR(VERSION)); + lua_pushstring(L, TRAINTASTIC_VERSION); lua_setfield(L, -2, "VERSION"); + lua_pushinteger(L, TRAINTASTIC_VERSION_MAJOR); + lua_setfield(L, -2, "VERSION_MAJOR"); + lua_pushinteger(L, TRAINTASTIC_VERSION_MINOR); + lua_setfield(L, -2, "VERSION_MINOR"); + lua_pushinteger(L, TRAINTASTIC_VERSION_PATCH); + lua_setfield(L, -2, "VERSION_PATCH"); // set CODENAME lua_pushstring(L, TRAINTASTIC_CODENAME); From c290837d334fc0fe2518d7f5cb0ab53f67eeca40 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 10 Nov 2021 23:49:22 +0100 Subject: [PATCH 20/43] lua: world: allow stop and power_off call from script --- server/src/world/world.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index ad4bba50..d349abb7 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -114,7 +114,7 @@ World::World(Private) : state.setValueInternal(state.value() + WorldState::Online); event(WorldEvent::Online); }}, - powerOff{*this, "power_off", + powerOff{*this, "power_off", MethodFlags::ScriptCallable, [this]() { Log::log(*this, LogMessage::N1014_POWER_ON); @@ -135,7 +135,7 @@ World::World(Private) : state.setValueInternal(state.value() + WorldState::Run); event(WorldEvent::Run); }}, - stop{*this, "stop", + stop{*this, "stop", MethodFlags::ScriptCallable, [this]() { Log::log(*this, LogMessage::N1017_STOPPED); @@ -174,7 +174,7 @@ World::World(Private) : event(WorldEvent::Smoke); } }}, - save{*this, "save", + save{*this, "save", MethodFlags::NoScript, [this]() { try From b6ec6cc8bd44155ab097842e09f77f282dea35ec Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 13 Nov 2021 00:35:56 +0100 Subject: [PATCH 21/43] lua: added LUA_VERSION global --- server/src/lua/sandbox.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index fc0f4739..0ddd0170 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -95,6 +95,11 @@ SandboxPtr Sandbox::create(Script& script) lua_pushstring(L, TRAINTASTIC_CODENAME); lua_setfield(L, -2, "CODENAME"); + // set LUA_VERSION + const std::string_view ident{lua_ident}; + push(L, ident.substr(13, ident.find('$', 13) - 14)); + lua_setfield(L, -2, "LUA_VERSION"); + // add world: push(L, std::static_pointer_cast<::Object>(script.world().lock())); lua_setfield(L, -2, "world"); From 906ff82cd82dd3f8946bfe43605d981d99391207 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 13 Nov 2021 00:39:45 +0100 Subject: [PATCH 22/43] lua: made built-in globals read only --- server/src/lua/error.hpp | 4 ++- server/src/lua/sandbox.cpp | 69 +++++++++++++++++++++++++++++++++++--- server/src/lua/sandbox.hpp | 2 ++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/server/src/lua/error.hpp b/server/src/lua/error.hpp index 17c12e7c..bb9268c6 100644 --- a/server/src/lua/error.hpp +++ b/server/src/lua/error.hpp @@ -42,7 +42,9 @@ namespace Lua { [[noreturn]] inline void errorExpectedNArgumentsGotN(lua_State* L, int expected, int got) { luaL_error(L, "expected %d arguments, got %d", expected, got); abort(); } -[[noreturn]] inline void errorException(lua_State* L, const std::exception& e) { luaL_error(L, "exceptiop: %s", e.what()); abort(); } +[[noreturn]] inline void errorException(lua_State* L, const std::exception& e) { luaL_error(L, "exception: %s", e.what()); abort(); } + +[[noreturn]] inline void errorGlobalNIsReadOnly(lua_State* L, const char* name) { luaL_error(L, "global %s is readonly", name); abort(); } [[noreturn]] inline void errorInternal(lua_State* L) { luaL_error(L, "internal error"); abort(); } diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index 0ddd0170..1d1d6716 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -26,6 +26,7 @@ #include "method.hpp" #include "log.hpp" #include "class.hpp" +#include "to.hpp" #include #include #include @@ -37,12 +38,38 @@ #include "../set/worldstate.hpp" #define LUA_SANDBOX "_sandbox" +#define LUA_SANDBOX_GLOBALS "_sandbox_globals" #define ADD_GLOBAL_TO_SANDBOX(x) \ lua_pushliteral(L, x); \ lua_getglobal(L, x); \ lua_settable(L, -3); +constexpr std::array readOnlyGlobals = {{ + // Lua baselib: + "assert", + "type", + "pairs", + "ipairs", + "_G", + // Constants: + "VERSION", + "VERSION_MAJOR", + "VERSION_MINOR", + "VERSION_PATCH", + "CODENAME", + "LUA_VERSION", + // Objects: + "world", + "log", + // Functions: + "is_instance", + // Type info: + "class", + "enum", + "set", +}}; + namespace Lua { void Sandbox::close(lua_State* L) @@ -51,6 +78,29 @@ void Sandbox::close(lua_State* L) lua_close(L); } +int Sandbox::__index(lua_State* L) +{ + lua_getglobal(L, LUA_SANDBOX_GLOBALS); + lua_replace(L, 1); + + lua_rawget(L, 1); + + return 1; +} + +int Sandbox::__newindex(lua_State* L) +{ + if(std::find(readOnlyGlobals.begin(), readOnlyGlobals.end(), to(L, 2)) != readOnlyGlobals.end()) + errorGlobalNIsReadOnly(L, lua_tostring(L, 2)); + + lua_getglobal(L, LUA_SANDBOX_GLOBALS); + lua_replace(L, 1); + + lua_rawset(L, 1); + + return 0; +} + SandboxPtr Sandbox::create(Script& script) { lua_State* L = luaL_newstate(); @@ -74,6 +124,16 @@ SandboxPtr Sandbox::create(Script& script) // setup sandbox: lua_newtable(L); + luaL_newmetatable(L, LUA_SANDBOX); + lua_pushcfunction(L, __index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, __newindex); + lua_setfield(L, -2, "__newindex"); + lua_setmetatable(L, -2); + lua_setglobal(L, LUA_SANDBOX); + + // setup globals: + lua_newtable(L); // add some Lua baselib functions to the sandbox: ADD_GLOBAL_TO_SANDBOX("assert") @@ -133,12 +193,11 @@ SandboxPtr Sandbox::create(Script& script) ReadOnlyTable::wrap(L, -1); lua_setfield(L, -2, "set"); - // let global _G point to itself: - lua_pushliteral(L, "_G"); - lua_pushvalue(L, -2); - lua_settable(L, -3); + // let global _G point to the sandbox: + lua_getglobal(L, LUA_SANDBOX); + lua_setfield(L, -2, "_G"); - lua_setglobal(L, LUA_SANDBOX); + lua_setglobal(L, LUA_SANDBOX_GLOBALS); return SandboxPtr(L, close); } diff --git a/server/src/lua/sandbox.hpp b/server/src/lua/sandbox.hpp index 4155222d..13e6ee56 100644 --- a/server/src/lua/sandbox.hpp +++ b/server/src/lua/sandbox.hpp @@ -36,6 +36,8 @@ class Sandbox { private: static void close(lua_State* L); + static int __index(lua_State* L); + static int __newindex(lua_State* L); public: class StateData From 5e18324f70fc840034cf88aac02c897872773be4 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 13 Nov 2021 23:25:45 +0100 Subject: [PATCH 23/43] fix: lua_ident was missing in lua53.def --- server/thirdparty/lua5.3/bin/win64/lua53.def | 1 + 1 file changed, 1 insertion(+) diff --git a/server/thirdparty/lua5.3/bin/win64/lua53.def b/server/thirdparty/lua5.3/bin/win64/lua53.def index 3148c0c3..2bb13492 100644 --- a/server/thirdparty/lua5.3/bin/win64/lua53.def +++ b/server/thirdparty/lua5.3/bin/win64/lua53.def @@ -27,6 +27,7 @@ lua_gettable lua_gettop lua_getupvalue lua_getuservalue +lua_ident lua_iscfunction lua_isinteger lua_isnumber From f64ec1296115576696544c7694c17b3bd81c3ca1 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 14 Nov 2021 01:12:39 +0100 Subject: [PATCH 24/43] lua: added math, string and table library to sandbox except string.dump() --- server/src/lua/sandbox.cpp | 75 ++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index 1d1d6716..137c02d4 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -40,18 +40,20 @@ #define LUA_SANDBOX "_sandbox" #define LUA_SANDBOX_GLOBALS "_sandbox_globals" -#define ADD_GLOBAL_TO_SANDBOX(x) \ - lua_pushliteral(L, x); \ - lua_getglobal(L, x); \ - lua_settable(L, -3); - -constexpr std::array readOnlyGlobals = {{ +constexpr std::array readOnlyGlobals = {{ // Lua baselib: "assert", "type", "pairs", "ipairs", + "next", + "tonumber", + "tostring", "_G", + // Lua libs: + LUA_MATHLIBNAME, + LUA_STRLIBNAME, + LUA_TABLIBNAME, // Constants: "VERSION", "VERSION_MAJOR", @@ -70,6 +72,40 @@ constexpr std::array readOnlyGlobals = {{ "set", }}; +static void addBaseLib(lua_State* L, std::initializer_list names) +{ + // load Lua baselib: + lua_pushcfunction(L, luaopen_base); + lua_pushliteral(L, ""); + lua_call(L, 1, 0); + + for(const char* name : names) + { + lua_getglobal(L, name); + assert(!lua_isnil(L, -1)); + lua_setfield(L, -2, name); + } +} + +static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunction, std::initializer_list names) +{ + lua_createtable(L, 0, names.size()); + + luaL_requiref(L, libraryName, openFunction, 1); + + for(const char* name : names) + { + lua_getfield(L, -1, name); + assert(!lua_isnil(L, -1)); + lua_setfield(L, -3, name); + } + + lua_pop(L, 1); // pop lib + + Lua::ReadOnlyTable::wrap(L, -1); + lua_setfield(L, -2, libraryName); +} + namespace Lua { void Sandbox::close(lua_State* L) @@ -105,11 +141,6 @@ SandboxPtr Sandbox::create(Script& script) { lua_State* L = luaL_newstate(); - // load Lua baselib: - lua_pushcfunction(L, luaopen_base); - lua_pushliteral(L, ""); - lua_call(L, 1, 0); - // create state data: *static_cast(lua_getextraspace(L)) = new StateData(script); @@ -135,11 +166,23 @@ SandboxPtr Sandbox::create(Script& script) // setup globals: lua_newtable(L); - // add some Lua baselib functions to the sandbox: - ADD_GLOBAL_TO_SANDBOX("assert") - ADD_GLOBAL_TO_SANDBOX("type") - ADD_GLOBAL_TO_SANDBOX("pairs") - ADD_GLOBAL_TO_SANDBOX("ipairs") + // add Lua lib functions: + addBaseLib(L, { + "assert", "type", "pairs", "ipairs", "next", "tonumber", "tostring", + }); + addLib(L, LUA_MATHLIBNAME, luaopen_math, { + "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", + "floor", "fmod", "huge", "log", "max", "maxinteger", "min", "mininteger", + "modf", "pi", "rad", "random", "randomseed", "sin", "sqrt", "tan", + "tointeger", "type", "ult", + }); + addLib(L, LUA_STRLIBNAME, luaopen_string, { + "byte", "char", "find", "format", "gmatch", "gsub", "len", "lower", + "match", "pack", "packsize", "rep", "reverse", "sub", "unpack", "upper", + }); + addLib(L, LUA_TABLIBNAME, luaopen_table, { + "concat", "insert", "pack", "unpack", "remove", "move", "sort", + }); // set VERSION: lua_pushstring(L, TRAINTASTIC_VERSION); From 56e3cdb68a860b6e34a9de1935c4a43e4fdea729 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 14 Nov 2021 01:26:54 +0100 Subject: [PATCH 25/43] updated lua globals for highlight --- client/src/widget/propertyluacodeedit.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/widget/propertyluacodeedit.cpp b/client/src/widget/propertyluacodeedit.cpp index 87b85c43..eb4bf432 100644 --- a/client/src/widget/propertyluacodeedit.cpp +++ b/client/src/widget/propertyluacodeedit.cpp @@ -182,7 +182,10 @@ PropertyLuaCodeEdit::Highlighter::Highlighter(QTextDocument* parent) : m_rules.append(Rule(regex, Qt::magenta)); const auto globals = { - QStringLiteral("\\log(?=\\.)"), + QStringLiteral("\\bmath(?=\\.)"), + QStringLiteral("\\bstring(?=\\.)"), + QStringLiteral("\\btable(?=\\.)"), + QStringLiteral("\\blog(?=\\.)"), QStringLiteral("\\bworld(?=\\.)"), QStringLiteral("\\bset(?=\\.)"), QStringLiteral("\\benum(?=\\.)"), From efc2c2a602f2b04096a5255dbcc3c31a8f9b0572 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 14 Nov 2021 01:27:16 +0100 Subject: [PATCH 26/43] fix: license and include guard --- client/src/board/boardcolorscheme.cpp | 22 ++++++++++++++++++++++ client/src/board/boardcolorscheme.hpp | 26 ++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/client/src/board/boardcolorscheme.cpp b/client/src/board/boardcolorscheme.cpp index 04917460..6e10e957 100644 --- a/client/src/board/boardcolorscheme.cpp +++ b/client/src/board/boardcolorscheme.cpp @@ -1,3 +1,25 @@ +/** + * client/src/board/boardcolorscheme.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + #include "boardcolorscheme.hpp" const BoardColorScheme BoardColorScheme::dark = { diff --git a/client/src/board/boardcolorscheme.hpp b/client/src/board/boardcolorscheme.hpp index a9fccb11..2f74fe76 100644 --- a/client/src/board/boardcolorscheme.hpp +++ b/client/src/board/boardcolorscheme.hpp @@ -1,5 +1,27 @@ -#ifndef ASD -#define ASD +/** + * client/src/board/boardcolorscheme.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_CLIENT_BOARD_BOARDCOLORSCHEME_HPP +#define TRAINTASTIC_CLIENT_BOARD_BOARDCOLORSCHEME_HPP #include From 4d6074aeb3bf326066bf6434d82ad3cdb7117425 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Tue, 16 Nov 2021 00:10:33 +0100 Subject: [PATCH 27/43] Added self build of lua53.dll with export of lua_ident --- server/thirdparty/lua5.3/bin/win64/lua53.dll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/thirdparty/lua5.3/bin/win64/lua53.dll b/server/thirdparty/lua5.3/bin/win64/lua53.dll index 8c8a54a6..2cfa543e 100644 --- a/server/thirdparty/lua5.3/bin/win64/lua53.dll +++ b/server/thirdparty/lua5.3/bin/win64/lua53.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86e6c1b8624c3db627f78a99966f64b41d446099e032834102726015b17f3bfc -size 330209 +oid sha256:3daf4aa54b28b5a9fd2102319686c0aec4d39b1ce30778f9a1f6a645935b7771 +size 203776 From f7f289e1d6b8bcccdee82adf2771a3b0353b38e4 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 20 Nov 2021 00:27:00 +0100 Subject: [PATCH 28/43] lua: added extended type() function and replaced in_instance() by get_class() --- server/src/lua/checkarguments.hpp | 39 +++++++ server/src/lua/class.cpp | 175 +++++++++++++++++++----------- server/src/lua/class.hpp | 14 ++- server/src/lua/sandbox.cpp | 38 +++++-- server/src/lua/type.cpp | 80 ++++++++++++++ server/src/lua/type.hpp | 34 ++++++ 6 files changed, 307 insertions(+), 73 deletions(-) create mode 100644 server/src/lua/checkarguments.hpp create mode 100644 server/src/lua/type.cpp create mode 100644 server/src/lua/type.hpp diff --git a/server/src/lua/checkarguments.hpp b/server/src/lua/checkarguments.hpp new file mode 100644 index 00000000..f3da69fd --- /dev/null +++ b/server/src/lua/checkarguments.hpp @@ -0,0 +1,39 @@ +/** + * server/src/lua/checkarguments.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_LUA_CHECKARGUMENTS_HPP +#define TRAINTASTIC_SERVER_LUA_CHECKARGUMENTS_HPP + +#include "error.hpp" + +namespace Lua { + +inline void checkArguments(lua_State* L, int count) +{ + const int top = lua_gettop(L); + if(top != count) + errorExpectedNArgumentsGotN(L, count, top); +} + +} + +#endif diff --git a/server/src/lua/class.cpp b/server/src/lua/class.cpp index 776e967f..22395097 100644 --- a/server/src/lua/class.cpp +++ b/server/src/lua/class.cpp @@ -23,6 +23,7 @@ #include "class.hpp" #include "push.hpp" #include "object.hpp" +#include "checkarguments.hpp" #include "../board/board.hpp" #include "../board/boardlist.hpp" @@ -126,96 +127,148 @@ static bool isInstance(const ::ObjectPtr& object) } template -inline static void setField(lua_State* L, std::string_view key) +inline static void registerValue(lua_State* L, std::string_view key) { static_assert(std::is_base_of_v<::Object, T>); - push(L, key); + + // add to global class (used by Class::push) + lua_getglobal(L, metaTableName); *reinterpret_cast(lua_newuserdata(L, sizeof(IsInstance))) = isInstance; - luaL_newmetatable(L, metaTableName); + if(luaL_newmetatable(L, metaTableName)) + { + lua_pushcfunction(L, Class::__tostring); + lua_setfield(L, -2, "__tostring"); + } lua_setmetatable(L, -2); + lua_rawsetp(L, -2, T::classId.data()); + lua_pop(L, 1); + + // add to sandbox class global: + Lua::push(L, key); + Class::push(L); lua_settable(L, -3); } void Class::registerValues(lua_State* L) { - assert(lua_istable(L, -1)); + assert(lua_istable(L, -1)); // sandboxes global class - setField(L, "BOARD"); - setField(L, "BOARD_LIST"); + lua_newtable(L); // global + lua_setglobal(L, metaTableName); - setField(L, "STRAIGHT_RAIL_TILE"); - setField(L, "TUNNEL_RAIL_TILE"); - setField(L, "BUFFER_STOP_RAIL_TILE"); - setField(L, "CURVE_45_RAIL_TILE"); - setField(L, "CURVE_90_RAIL_TILE"); - setField(L, "CROSS_45_RAIL_TILE"); - setField(L, "CROSS_90_RAIL_TILE"); - setField(L, "BRIDGE_45_RIGHT_RAIL_TILE"); - setField(L, "BRIDGE_45_LEFT_RAIL_TILE"); - setField(L, "BRIDGE_90_RAIL_TILE"); - setField(L, "TURNOUT_SINGLE_SLIP_RAIL_TILE"); - setField(L, "TURNOUT_LEFT_90_RAIL_TILE"); - setField(L, "TURNOUT_RIGHT_45_RAIL_TILE"); - setField(L, "TURNOUT_LEFT_45_RAIL_TILE"); - setField(L, "TURNOUT_RIGHT_90_RAIL_TILE"); - setField(L, "TURNOUT_DOUBLE_SLIP_RAIL_TILE"); - setField(L, "TURNOUT_WYE_RAIL_TILE"); - setField(L, "TURNOUT__RAIL_TILE"); - setField(L, "TURNOUT_3WAY_RAIL_TILE"); - setField(L, "TURNOUT_RIGHT_CURVED_RAIL_TILE"); - setField(L, "SIGNAL_2_ASPECT_RAIL_TILE"); - setField(L, "SIGNAL_3_ASPECT_RAIL_TILE"); - setField(L, "SENSOR_RAIL_TILE"); - setField(L, "BLOCK_RAIL_TILE"); + registerValue(L, "BOARD"); + registerValue(L, "BOARD_LIST"); - setField(L, "CLOCK"); + registerValue(L, "STRAIGHT_RAIL_TILE"); + registerValue(L, "TUNNEL_RAIL_TILE"); + registerValue(L, "BUFFER_STOP_RAIL_TILE"); + registerValue(L, "CURVE_45_RAIL_TILE"); + registerValue(L, "CURVE_90_RAIL_TILE"); + registerValue(L, "CROSS_45_RAIL_TILE"); + registerValue(L, "CROSS_90_RAIL_TILE"); + registerValue(L, "BRIDGE_45_RIGHT_RAIL_TILE"); + registerValue(L, "BRIDGE_45_LEFT_RAIL_TILE"); + registerValue(L, "BRIDGE_90_RAIL_TILE"); + registerValue(L, "TURNOUT_SINGLE_SLIP_RAIL_TILE"); + registerValue(L, "TURNOUT_LEFT_90_RAIL_TILE"); + registerValue(L, "TURNOUT_RIGHT_45_RAIL_TILE"); + registerValue(L, "TURNOUT_LEFT_45_RAIL_TILE"); + registerValue(L, "TURNOUT_RIGHT_90_RAIL_TILE"); + registerValue(L, "TURNOUT_DOUBLE_SLIP_RAIL_TILE"); + registerValue(L, "TURNOUT_WYE_RAIL_TILE"); + registerValue(L, "TURNOUT__RAIL_TILE"); + registerValue(L, "TURNOUT_3WAY_RAIL_TILE"); + registerValue(L, "TURNOUT_RIGHT_CURVED_RAIL_TILE"); + registerValue(L, "SIGNAL_2_ASPECT_RAIL_TILE"); + registerValue(L, "SIGNAL_3_ASPECT_RAIL_TILE"); + registerValue(L, "SENSOR_RAIL_TILE"); + registerValue(L, "BLOCK_RAIL_TILE"); - setField(L, "WLANMAUS_CONTROLLER"); - setField(L, "CONTROLLER_LIST"); + registerValue(L, "CLOCK"); + + registerValue(L, "WLANMAUS_CONTROLLER"); + registerValue(L, "CONTROLLER_LIST"); #ifdef USB_XPRESSNET - setField(L, "USB_XPRESSNET_CONTROLLER"); + registerValue(L, "USB_XPRESSNET_CONTROLLER"); #endif - setField(L, "Z21_COMMAND_STATION"); - setField(L, "VIRTUAL_COMMAND_STATION"); - setField(L, "LOCONET_TCP_BINARY_COMMAND_STATION"); + registerValue(L, "Z21_COMMAND_STATION"); + registerValue(L, "VIRTUAL_COMMAND_STATION"); + registerValue(L, "LOCONET_TCP_BINARY_COMMAND_STATION"); #ifdef USB_XPRESSNET - setField(L, "USB_XPRESSNET_INTERFACE"); + registerValue(L, "USB_XPRESSNET_INTERFACE"); #endif - setField(L, "XPRESSNET_SERIAL_COMMAND_STATION"); - setField(L, "DCCPLUSPLUS_SERIAL_COMMAND_STATION"); - setField(L, "COMMAND_STATION_LIST"); - setField(L, "LOCONET_SERIAL_COMMAND_STATION"); + registerValue(L, "XPRESSNET_SERIAL_COMMAND_STATION"); + registerValue(L, "DCCPLUSPLUS_SERIAL_COMMAND_STATION"); + registerValue(L, "COMMAND_STATION_LIST"); + registerValue(L, "LOCONET_SERIAL_COMMAND_STATION"); - setField(L, "DECODER_FUNCTION"); - setField(L, "DECODER_LIST"); - setField(L, "DECODER"); - setField(L, "DECODER_FUNCTIONS"); + registerValue(L, "DECODER_FUNCTION"); + registerValue(L, "DECODER_LIST"); + registerValue(L, "DECODER"); + registerValue(L, "DECODER_FUNCTIONS"); - setField(L, "LOCONET_INPUT"); - setField(L, "XPRESSNET_INPUT"); - setField(L, "INPUT_LIST"); + registerValue(L, "LOCONET_INPUT"); + registerValue(L, "XPRESSNET_INPUT"); + registerValue(L, "INPUT_LIST"); - setField(L, "OUTPUT_LIST"); - setField(L, "LOCONET_OUTPUT"); + registerValue(L, "OUTPUT_LIST"); + registerValue(L, "LOCONET_OUTPUT"); - setField(L, "RAIL_VEHICLE_LIST"); - setField(L, "LOCOMOTIVE"); - setField(L, "FREIGHT_CAR"); + registerValue(L, "RAIL_VEHICLE_LIST"); + registerValue(L, "LOCOMOTIVE"); + registerValue(L, "FREIGHT_CAR"); - setField(L, "TRAIN"); - setField(L, "TRAIN_LIST"); + registerValue(L, "TRAIN"); + registerValue(L, "TRAIN_LIST"); - setField(L, "WORLD"); + registerValue(L, "WORLD"); } -int Class::isInstance(lua_State* L) +void Class::push(lua_State* L, std::string_view classId) { - if(lua_gettop(L) != 2) - errorExpectedNArgumentsGotN(L, 2, lua_gettop(L)); + lua_getglobal(L, metaTableName); + assert(lua_istable(L, -1)); + lua_rawgetp(L, -1, classId.data()); + assert(lua_isuserdata(L, -1)); + lua_insert(L, lua_gettop(L) - 1); + lua_pop(L, 1); + assert(lua_isuserdata(L, -1)); +} - push(L, (*reinterpret_cast(luaL_checkudata(L, 2, metaTableName)))(Object::test(L, 1))); +void Class::push(lua_State* L, const ObjectPtr& object) +{ + push(L, object->getClassId()); +} +int Class::__tostring(lua_State* L) +{ + Sandbox::getGlobal(L, metaTableName); + + // get the real table, the global is wrapped for write protection: + lua_getmetatable(L, -1); + lua_getfield(L, -1, "__index"); + assert(lua_istable(L, -1)); + + // loop over table to find value and the return key + const int idx = lua_gettop(L); + lua_pushnil(L); + while(lua_next(L, idx)) + { + const bool eq = lua_compare(L, 1, -1, LUA_OPEQ); + lua_pop(L, 1); // pop value + if(eq) + return 1; + } + + lua_pushlstring(L, NULL, 0); + return 1; +} + +int Class::getClass(lua_State* L) +{ + checkArguments(L, 1); + push(L, Object::check(L, 1)); return 1; } diff --git a/server/src/lua/class.hpp b/server/src/lua/class.hpp index 3a0b55c8..3c487b08 100644 --- a/server/src/lua/class.hpp +++ b/server/src/lua/class.hpp @@ -32,7 +32,19 @@ struct Class { static void registerValues(lua_State* L); - static int isInstance(lua_State* L); + static void push(lua_State* L, std::string_view classId); + static void push(lua_State* L, const ObjectPtr& object); + + template + static void push(lua_State* L) + { + static_assert(std::is_base_of_v<::Object, T>); + push(L, T::classId); + } + + static int __tostring(lua_State* L); + + static int getClass(lua_State* L); }; } diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index 137c02d4..9ba096bd 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -27,6 +27,7 @@ #include "log.hpp" #include "class.hpp" #include "to.hpp" +#include "type.hpp" #include #include #include @@ -72,7 +73,17 @@ constexpr std::array readOnlyGlobals = {{ "set", }}; -static void addBaseLib(lua_State* L, std::initializer_list names) +static void addExtensions(lua_State* L, std::initializer_list> extensions) +{ + assert(lua_istable(L, -1)); + for(auto [name, func] : extensions) + { + lua_pushcfunction(L, func); + lua_setfield(L, -2, name); + } +} + +static void addBaseLib(lua_State* L, std::initializer_list names, std::initializer_list> extensions = {}) { // load Lua baselib: lua_pushcfunction(L, luaopen_base); @@ -85,11 +96,13 @@ static void addBaseLib(lua_State* L, std::initializer_list names) assert(!lua_isnil(L, -1)); lua_setfield(L, -2, name); } + + addExtensions(L, extensions); } -static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunction, std::initializer_list names) +static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunction, std::initializer_list names, std::initializer_list> extensions = {}) { - lua_createtable(L, 0, names.size()); + lua_createtable(L, 0, names.size() + extensions.size()); luaL_requiref(L, libraryName, openFunction, 1); @@ -102,6 +115,8 @@ static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunc lua_pop(L, 1); // pop lib + addExtensions(L, extensions); + Lua::ReadOnlyTable::wrap(L, -1); lua_setfield(L, -2, libraryName); } @@ -166,9 +181,14 @@ SandboxPtr Sandbox::create(Script& script) // setup globals: lua_newtable(L); - // add Lua lib functions: - addBaseLib(L, { - "assert", "type", "pairs", "ipairs", "next", "tonumber", "tostring", + // add standard Lua lib functions and extensions/replacements: + addBaseLib(L, + { + "assert", "pairs", "ipairs", "next", "tonumber", "tostring", + }, + { + {"type", type}, + {"get_class", Class::getClass}, }); addLib(L, LUA_MATHLIBNAME, luaopen_math, { "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", @@ -211,10 +231,6 @@ SandboxPtr Sandbox::create(Script& script) Log::push(L); lua_setfield(L, -2, "log"); - // add is_instance function: - lua_pushcfunction(L, Class::isInstance); - lua_setfield(L, -2, "is_instance"); - // add class types: lua_newtable(L); Class::registerValues(L); @@ -252,7 +268,7 @@ Sandbox::StateData& Sandbox::getStateData(lua_State* L) int Sandbox::getGlobal(lua_State* L, const char* name) { - lua_getglobal(L, LUA_SANDBOX); // get the sandbox + lua_getglobal(L, LUA_SANDBOX_GLOBALS); // get the sandbox lua_pushstring(L, name); const int type = lua_gettable(L, -2); // get item lua_insert(L, lua_gettop(L) - 1); // swap item and sandbox on the stack diff --git a/server/src/lua/type.cpp b/server/src/lua/type.cpp new file mode 100644 index 00000000..cb048934 --- /dev/null +++ b/server/src/lua/type.cpp @@ -0,0 +1,80 @@ +/** + * server/src/lua/type.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "type.hpp" +#include "object.hpp" +#include "class.hpp" +#include "sandbox.hpp" + +namespace Lua { + +int type(lua_State* L) +{ + const int tp = lua_type(L, 1); + + if(tp == LUA_TUSERDATA) + { + if(auto object = Object::test(L, 1)) + { + lua_pushstring(L, "object"); + Class::push(L, object); + return 2; + } + + if(lua_getmetatable(L, 1)) + { + lua_getfield(L, -1, "__name"); + const char* name = lua_tostring(L, -1); + + // check for enum: + Sandbox::getGlobal(L, "enum"); + lua_getfield(L, -1, name); + if(lua_istable(L, -1)) + { + lua_pop(L, 2); + lua_pushstring(L, "enum"); + lua_replace(L, -3); + return 2; + } + else + lua_pop(L, 2); + + // check for set: + Sandbox::getGlobal(L, "set"); + lua_getfield(L, -1, name); + if(lua_istable(L, -1)) + { + lua_pop(L, 2); + lua_pushstring(L, "set"); + lua_replace(L, -3); + return 2; + } + else + lua_pop(L, 2); + } + } + + lua_pushstring(L, lua_typename(L, tp)); + return 1; +} + +} diff --git a/server/src/lua/type.hpp b/server/src/lua/type.hpp new file mode 100644 index 00000000..bf0990b5 --- /dev/null +++ b/server/src/lua/type.hpp @@ -0,0 +1,34 @@ +/** + * server/src/lua/type.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_LUA_TYPE_HPP +#define TRAINTASTIC_SERVER_LUA_TYPE_HPP + +#include + +namespace Lua { + +int type(lua_State* L); + +} + +#endif From f1c2b5fa55bda0c49f74e16aaa9874fd4c685375 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 20 Nov 2021 00:28:11 +0100 Subject: [PATCH 29/43] lua: added property script writable check --- server/src/lua/object.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index 4f33ce0c..62c2fde0 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -183,9 +183,7 @@ int Object::__newindex(lua_State* L) if(AbstractProperty* property = object->getProperty(name)) { - // TODO: test scriptable - - if(!property->isWriteable()) + if(!property->isScriptWriteable() || !property->isWriteable()) errorCantSetReadOnlyProperty(L); try From a072bdef76fd12267401ae5aef7ecb2ff015a68d Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Tue, 23 Nov 2021 23:50:54 +0100 Subject: [PATCH 30/43] test: lua is_instance -> get_class, fixed implementation issues --- server/src/lua/class.cpp | 6 +- server/test/lua/getclass.cpp | 132 ++++++++++++++++++++++++++++ server/test/lua/isinstance.cpp | 156 --------------------------------- 3 files changed, 136 insertions(+), 158 deletions(-) create mode 100644 server/test/lua/getclass.cpp delete mode 100644 server/test/lua/isinstance.cpp diff --git a/server/src/lua/class.cpp b/server/src/lua/class.cpp index 22395097..52825416 100644 --- a/server/src/lua/class.cpp +++ b/server/src/lua/class.cpp @@ -133,6 +133,7 @@ inline static void registerValue(lua_State* L, std::string_view key) // add to global class (used by Class::push) lua_getglobal(L, metaTableName); + Lua::push(L, T::classId); *reinterpret_cast(lua_newuserdata(L, sizeof(IsInstance))) = isInstance; if(luaL_newmetatable(L, metaTableName)) { @@ -140,7 +141,7 @@ inline static void registerValue(lua_State* L, std::string_view key) lua_setfield(L, -2, "__tostring"); } lua_setmetatable(L, -2); - lua_rawsetp(L, -2, T::classId.data()); + lua_rawset(L, -3); lua_pop(L, 1); // add to sandbox class global: @@ -229,7 +230,8 @@ void Class::push(lua_State* L, std::string_view classId) { lua_getglobal(L, metaTableName); assert(lua_istable(L, -1)); - lua_rawgetp(L, -1, classId.data()); + Lua::push(L, classId); + lua_rawget(L, -2); assert(lua_isuserdata(L, -1)); lua_insert(L, lua_gettop(L) - 1); lua_pop(L, 1); diff --git a/server/test/lua/getclass.cpp b/server/test/lua/getclass.cpp new file mode 100644 index 00000000..ad8e981b --- /dev/null +++ b/server/test/lua/getclass.cpp @@ -0,0 +1,132 @@ +/** + * server/test/lua/getclass.cpp + * + * This file is part of the traintastic test suite. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "../../src/lua/class.hpp" +#include "../../src/lua/object.hpp" +#include "../../src/world/world.hpp" + +TEST_CASE("Lua get_class", "[lua][lua-get_class]") +{ + static const char* get_class = "get_class"; + static const char* WORLD = "WORLD"; + static const char* DECODER = "DECODER"; + + lua_State* L = luaL_newstate(); + + // add in_instance function + lua_pushcfunction(L, Lua::Class::getClass); + lua_setglobal(L, get_class); + + // add classes + lua_newtable(L); + Lua::Class::registerValues(L); + + // register object type + Lua::Object::registerType(L); + + // nil + lua_getglobal(L, get_class); + lua_pushnil(L); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // boolean: false + lua_getglobal(L, get_class); + lua_pushboolean(L, 0); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // boolean: true + lua_getglobal(L, get_class); + lua_pushboolean(L, 1); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // integer: 42 + lua_getglobal(L, get_class); + lua_pushinteger(L, 42); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // number: 3.14 + lua_getglobal(L, get_class); + lua_pushnumber(L, 3.14); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // string + lua_getglobal(L, get_class); + lua_pushstring(L, "traintastic"); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // table + lua_getglobal(L, get_class); + lua_newtable(L); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // userdata + lua_getglobal(L, get_class); + lua_newuserdata(L, sizeof(void*)); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_ERRRUN); + lua_pop(L, 1); + + // World + auto world = World::create(); + lua_getglobal(L, get_class); + Lua::Object::push(L, world); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_OK); + REQUIRE(lua_isuserdata(L, -1)); + lua_getfield(L, -2, WORLD); + REQUIRE(lua_rawequal(L, -1, -2) == 1); + lua_pop(L, 2); + + lua_getglobal(L, get_class); + Lua::Object::push(L, world); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_OK); + REQUIRE(lua_isuserdata(L, -1)); + lua_getfield(L, -2, DECODER); + REQUIRE(lua_rawequal(L, -1, -2) == 0); + lua_pop(L, 2); + + // Decoder + auto decoder = world->decoders->add(); + lua_getglobal(L, get_class); + Lua::Object::push(L, decoder); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_OK); + REQUIRE(lua_isuserdata(L, -1)); + lua_getfield(L, -2, WORLD); + REQUIRE(lua_rawequal(L, -1, -2) == 0); + lua_pop(L, 2); + + lua_getglobal(L, get_class); + Lua::Object::push(L, decoder); + REQUIRE(lua_pcall(L, 1, 1, 0) == LUA_OK); + REQUIRE(lua_isuserdata(L, -1)); + lua_getfield(L, -2, DECODER); + REQUIRE(lua_rawequal(L, -1, -2) == 1); + lua_pop(L, 2); + + lua_close(L); +} diff --git a/server/test/lua/isinstance.cpp b/server/test/lua/isinstance.cpp deleted file mode 100644 index 455f9134..00000000 --- a/server/test/lua/isinstance.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/** - * server/test/lua/isinstance.cpp - * - * This file is part of the traintastic test suite. - * - * Copyright (C) 2021 Reinder Feenstra - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include "../../src/lua/class.hpp" -#include "../../src/lua/object.hpp" -#include "../../src/world/world.hpp" - -TEST_CASE("Lua is_instance", "[lua][lua-isinstance]") -{ - static const char* is_instance = "is_instance"; - static const char* WORLD = "WORLD"; - static const char* DECODER = "DECODER"; - - lua_State* L = luaL_newstate(); - - // add in_instance function - lua_pushcfunction(L, Lua::Class::isInstance); - lua_setglobal(L, is_instance); - - // add classes - lua_newtable(L); - Lua::Class::registerValues(L); - - // register object type - Lua::Object::registerType(L); - - // nil - lua_getglobal(L, is_instance); - lua_pushnil(L); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // boolean: false - lua_getglobal(L, is_instance); - lua_pushboolean(L, 0); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // boolean: true - lua_getglobal(L, is_instance); - lua_pushboolean(L, 1); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // integer: 42 - lua_getglobal(L, is_instance); - lua_pushinteger(L, 42); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // number: 3.14 - lua_getglobal(L, is_instance); - lua_pushnumber(L, 3.14); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // string - lua_getglobal(L, is_instance); - lua_pushstring(L, "traintastic"); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // table - lua_getglobal(L, is_instance); - lua_newtable(L); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // userdata - lua_getglobal(L, is_instance); - lua_newuserdata(L, sizeof(void*)); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // World - auto world = World::create(); - lua_getglobal(L, is_instance); - Lua::Object::push(L, world); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - lua_getglobal(L, is_instance); - Lua::Object::push(L, world); - lua_getfield(L, -3, DECODER); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - // Decoder - auto decoder = world->decoders->add(); - lua_getglobal(L, is_instance); - Lua::Object::push(L, decoder); - lua_getfield(L, -3, WORLD); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE_FALSE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - lua_getglobal(L, is_instance); - Lua::Object::push(L, decoder); - lua_getfield(L, -3, DECODER); - REQUIRE(lua_pcall(L, 2, 1, 0) == LUA_OK); - REQUIRE(lua_isboolean(L, -1)); - REQUIRE(lua_toboolean(L, -1)); - lua_pop(L, 1); - - lua_close(L); -} From c031dfabcd14093abe543ed267b4984d2c920c3c Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 24 Nov 2021 00:02:21 +0100 Subject: [PATCH 31/43] core: added event support --- server/src/core/abstractevent.cpp | 56 ++++++++++++++++ server/src/core/abstractevent.hpp | 59 +++++++++++++++++ server/src/core/abstracteventhandler.cpp | 34 ++++++++++ server/src/core/abstracteventhandler.hpp | 50 ++++++++++++++ server/src/core/abstractmethod.hpp | 5 +- server/src/core/argument.hpp | 35 ++++++++++ server/src/core/event.hpp | 83 ++++++++++++++++++++++++ server/src/core/eventflags.hpp | 37 +++++++++++ server/src/core/method.hpp | 2 +- server/src/core/object.hpp | 7 ++ server/src/core/session.cpp | 2 +- 11 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 server/src/core/abstractevent.cpp create mode 100644 server/src/core/abstractevent.hpp create mode 100644 server/src/core/abstracteventhandler.cpp create mode 100644 server/src/core/abstracteventhandler.hpp create mode 100644 server/src/core/argument.hpp create mode 100644 server/src/core/event.hpp create mode 100644 server/src/core/eventflags.hpp diff --git a/server/src/core/abstractevent.cpp b/server/src/core/abstractevent.cpp new file mode 100644 index 00000000..31a24d0b --- /dev/null +++ b/server/src/core/abstractevent.cpp @@ -0,0 +1,56 @@ +/** + * server/src/core/abstractevent.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "abstractevent.hpp" +#include "abstracteventhandler.hpp" + +AbstractEvent::AbstractEvent(Object& object, const std::string& name, EventFlags flags) + : InterfaceItem(object, name) + , m_flags{flags} +{ +} + +void AbstractEvent::connect(std::shared_ptr handler) +{ + assert(handler); + assert(&handler->event() == this); + m_handlers.emplace_back(std::move(handler)); +} + +bool AbstractEvent::disconnect(const std::shared_ptr& handler) +{ + auto it = std::find(m_handlers.begin(), m_handlers.end(), handler); + if(it != m_handlers.end()) + { + m_handlers.erase(it); + return true; + } + else + return false; +} + +void AbstractEvent::fire(const Arguments& args) +{ + const auto handlers{m_handlers}; // copy, list can be modified while iterating + for(const auto& handler : handlers) + handler->execute(args); +} diff --git a/server/src/core/abstractevent.hpp b/server/src/core/abstractevent.hpp new file mode 100644 index 00000000..4d4c8938 --- /dev/null +++ b/server/src/core/abstractevent.hpp @@ -0,0 +1,59 @@ +/** + * server/src/core/abstractevent.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_CORE_ABSTRACTEVENT_HPP +#define TRAINTASTIC_SERVER_CORE_ABSTRACTEVENT_HPP + +#include "interfaceitem.hpp" +#include +#include "eventflags.hpp" +#include "argument.hpp" + +class AbstractEventHandler; + +class AbstractEvent : public InterfaceItem +{ + private: + const EventFlags m_flags; + std::list> m_handlers; + bool m_firing; + + protected: + void fire(const Arguments& args); + + public: + using ArgumentInfo = std::pair; + + AbstractEvent(Object& object, const std::string& name, EventFlags m_flags); + + inline bool isScriptable() const { return (m_flags & EventFlags::Scriptable) == EventFlags::Scriptable; } + + inline EventFlags flags() const { return m_flags; } + + /// @todo C++20 -> std::span ?? + virtual std::pair argumentInfo() const = 0; + + void connect(std::shared_ptr handler); + bool disconnect(const std::shared_ptr& handler); +}; + +#endif diff --git a/server/src/core/abstracteventhandler.cpp b/server/src/core/abstracteventhandler.cpp new file mode 100644 index 00000000..2512f2d3 --- /dev/null +++ b/server/src/core/abstracteventhandler.cpp @@ -0,0 +1,34 @@ +/** + * server/src/core/abstracteventhandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "abstracteventhandler.hpp" +#include "abstractevent.hpp" + +AbstractEventHandler::AbstractEventHandler(AbstractEvent& evt) + : m_event{evt} +{ +} + +bool AbstractEventHandler::disconnect() +{ + return m_event.disconnect(shared_from_this()); +} diff --git a/server/src/core/abstracteventhandler.hpp b/server/src/core/abstracteventhandler.hpp new file mode 100644 index 00000000..d1367921 --- /dev/null +++ b/server/src/core/abstracteventhandler.hpp @@ -0,0 +1,50 @@ +/** + * server/src/core/abstracteventhandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_CORE_ABSTRACTEVENTHANDLER_HPP +#define TRAINTASTIC_SERVER_CORE_ABSTRACTEVENTHANDLER_HPP + +#include "argument.hpp" + +class AbstractEvent; + +class AbstractEventHandler : public std::enable_shared_from_this +{ + protected: + AbstractEvent& m_event; + + public: + AbstractEventHandler(const AbstractEventHandler&) = delete; + AbstractEventHandler& operator =(const AbstractEventHandler&) = delete; + + AbstractEventHandler(AbstractEvent& evt); + + virtual ~AbstractEventHandler() = default; + + AbstractEvent& event() const { return m_event; } + + virtual void execute(const Arguments& args) = 0; + + virtual bool disconnect(); +}; + +#endif diff --git a/server/src/core/abstractmethod.hpp b/server/src/core/abstractmethod.hpp index eb31b004..54c114ab 100644 --- a/server/src/core/abstractmethod.hpp +++ b/server/src/core/abstractmethod.hpp @@ -28,7 +28,7 @@ #include #include #include -#include "objectptr.hpp" +#include "argument.hpp" class AbstractMethod : public InterfaceItem { @@ -95,7 +95,6 @@ class AbstractMethod : public InterfaceItem } }; - using Argument = std::variant; using Result = std::variant; AbstractMethod(Object& object, const std::string& name, MethodFlags m_flags = noMethodFlags); @@ -107,7 +106,7 @@ class AbstractMethod : public InterfaceItem virtual std::size_t argumentCount() const = 0; virtual std::vector argumentTypes() const = 0; virtual ValueType resultType() const = 0; - virtual Result call(const std::vector& args) = 0; + virtual Result call(const Arguments& args) = 0; }; #endif diff --git a/server/src/core/argument.hpp b/server/src/core/argument.hpp new file mode 100644 index 00000000..0cea71d6 --- /dev/null +++ b/server/src/core/argument.hpp @@ -0,0 +1,35 @@ +/** + * server/src/core/argument.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_CORE_ARGUMENT_HPP +#define TRAINTASTIC_SERVER_CORE_ARGUMENT_HPP + +#include +#include +#include +#include +#include "objectptr.hpp" + +using Argument = std::variant; +using Arguments = std::vector; + +#endif diff --git a/server/src/core/event.hpp b/server/src/core/event.hpp new file mode 100644 index 00000000..b079bccb --- /dev/null +++ b/server/src/core/event.hpp @@ -0,0 +1,83 @@ +/** + * server/src/core/event.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_CORE_EVENT_HPP +#define TRAINTASTIC_SERVER_CORE_EVENT_HPP + +#include "abstractevent.hpp" + +template +class Event : public AbstractEvent +{ + friend class Object; + + private: + template + inline void addArguments(Arguments& args, T value, Tn... others) + { + if constexpr(value_type_v == ValueType::Enum || value_type_v == ValueType::Set) + args.emplace_back(static_cast(value)); + else + args.emplace_back(value); + + if constexpr(sizeof...(Tn) > 0) + addArguments(args, others...); + } + + template + static constexpr std::pair getArgumentInfo() + { + if constexpr(is_set_v) + return {ValueType::Set, set_name_v}; + else if constexpr(std::is_enum_v) + return {ValueType::Enum, EnumName::value}; + else + return {value_type_v, {}}; + } + + static constexpr std::array s_argumentInfo = {{getArgumentInfo()...}}; + + protected: + void fire(Args... args) + { + Arguments arguments; + if constexpr(sizeof...(Args) > 0) + { + arguments.reserve(sizeof...(Args)); + addArguments(arguments, args...); + } + AbstractEvent::fire(arguments); + } + + public: + Event(Object& object, const std::string& name, EventFlags flags) : + AbstractEvent(object, name, flags) + { + } + + std::pair argumentInfo() const final + { + return {s_argumentInfo.data(), s_argumentInfo.size()}; + } +}; + +#endif diff --git a/server/src/core/eventflags.hpp b/server/src/core/eventflags.hpp new file mode 100644 index 00000000..ee32021d --- /dev/null +++ b/server/src/core/eventflags.hpp @@ -0,0 +1,37 @@ +/** + * server/src/core/eventflags.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_CORE_EVENTFLAGS_HPP +#define TRAINTASTIC_SERVER_CORE_EVENTFLAGS_HPP + +enum class EventFlags +{ + // bit 0 + Scriptable = 1 << 0, +}; + +constexpr EventFlags operator &(EventFlags lhs, EventFlags rhs) +{ + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +#endif diff --git a/server/src/core/method.hpp b/server/src/core/method.hpp index 50891185..7c9b9dc0 100644 --- a/server/src/core/method.hpp +++ b/server/src/core/method.hpp @@ -44,7 +44,7 @@ template using getArgumentType = typename std::tuple_element>::type; template -auto getArgument(const AbstractMethod::Argument& value) +auto getArgument(const Argument& value) { using T = std::remove_const_t>>; diff --git a/server/src/core/object.hpp b/server/src/core/object.hpp index b8abf339..2a0b5875 100644 --- a/server/src/core/object.hpp +++ b/server/src/core/object.hpp @@ -35,6 +35,7 @@ static constexpr std::string_view classId = id; \ std::string_view getClassId() const override { return classId; } +template class Event; class AbstractMethod; class BaseProperty; class AbstractProperty; @@ -55,6 +56,12 @@ class Object : public std::enable_shared_from_this protected: InterfaceItems m_interfaceItems; + template + inline void fireEvent(Event& event, Args... args) + { + event.fire(std::forward(args)...); + } + inline bool dying() const noexcept { return m_dying; } virtual void destroying() {} virtual void load(WorldLoader& loader, const nlohmann::json& data); diff --git a/server/src/core/session.cpp b/server/src/core/session.cpp index fed962be..01876e29 100644 --- a/server/src/core/session.cpp +++ b/server/src/core/session.cpp @@ -261,7 +261,7 @@ bool Session::processMessage(const Message& message) const ValueType resultType = message.read(); const uint8_t argumentCount = message.read(); - std::vector args; + Arguments args; for(uint8_t i = 0; i < argumentCount; i++) { switch(message.read()) From c5dac66570a13f8c927deb53de6f78430b6d1278 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 24 Nov 2021 00:02:56 +0100 Subject: [PATCH 32/43] world: added on_event event --- server/src/world/world.cpp | 5 +++++ server/src/world/world.hpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index d349abb7..1fd4f30b 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -229,6 +229,7 @@ World::World(Private) : Log::log(*this, LogMessage::C1005_SAVING_WORLD_FAILED_X, e); } }} + , onEvent{*this, "on_event", EventFlags::Scriptable} { Attributes::addDisplayName(uuid, DisplayName::World::uuid); m_interfaceItems.add(uuid); @@ -292,6 +293,8 @@ World::World(Private) : Attributes::addObjectEditor(save, false); m_interfaceItems.add(save); + + m_interfaceItems.add(onEvent); } std::string World::getUniqueId(std::string_view prefix) const @@ -372,6 +375,8 @@ void World::worldEvent(WorldState worldState, WorldEvent worldEvent) Attributes::setEnabled(scale, editState && !runState); Attributes::setEnabled(scaleRatio, editState && !runState); + + fireEvent(onEvent, worldState, worldEvent); } void World::event(const WorldEvent value) diff --git a/server/src/world/world.hpp b/server/src/world/world.hpp index c70e658a..6707a77c 100644 --- a/server/src/world/world.hpp +++ b/server/src/world/world.hpp @@ -26,6 +26,7 @@ #include "../core/object.hpp" #include "../core/property.hpp" #include "../core/objectproperty.hpp" +#include "../core/event.hpp" #include #include #include @@ -113,6 +114,8 @@ class World : public Object Method save; + Event onEvent; + World(Private); std::string getObjectId() const final { return std::string(classId); } From 63eec6d46123115c72a440f22a946975d15f878f Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 24 Nov 2021 00:07:36 +0100 Subject: [PATCH 33/43] lua: added event support --- server/src/lua/checkarguments.hpp | 8 ++ server/src/lua/error.hpp | 2 + server/src/lua/event.cpp | 134 ++++++++++++++++++++++++++++++ server/src/lua/event.hpp | 53 ++++++++++++ server/src/lua/eventhandler.cpp | 128 ++++++++++++++++++++++++++++ server/src/lua/eventhandler.hpp | 51 ++++++++++++ server/src/lua/log.cpp | 3 + server/src/lua/method.cpp | 2 +- server/src/lua/object.cpp | 9 ++ server/src/lua/sandbox.cpp | 15 +++- server/src/lua/sandbox.hpp | 49 ++++++++++- server/src/lua/script.cpp | 13 --- 12 files changed, 450 insertions(+), 17 deletions(-) create mode 100644 server/src/lua/event.cpp create mode 100644 server/src/lua/event.hpp create mode 100644 server/src/lua/eventhandler.cpp create mode 100644 server/src/lua/eventhandler.hpp diff --git a/server/src/lua/checkarguments.hpp b/server/src/lua/checkarguments.hpp index f3da69fd..8a726e4b 100644 --- a/server/src/lua/checkarguments.hpp +++ b/server/src/lua/checkarguments.hpp @@ -34,6 +34,14 @@ inline void checkArguments(lua_State* L, int count) errorExpectedNArgumentsGotN(L, count, top); } +inline int checkArguments(lua_State* L, int min, int max) +{ + const int top = lua_gettop(L); + if(top < min || top > max) + errorExpectedNNArgumentsGotN(L, min, max, top); + return top; +} + } #endif diff --git a/server/src/lua/error.hpp b/server/src/lua/error.hpp index bb9268c6..75adabd1 100644 --- a/server/src/lua/error.hpp +++ b/server/src/lua/error.hpp @@ -37,10 +37,12 @@ namespace Lua { [[noreturn]] inline void errorCantSetNonExistingProperty(lua_State* L) { luaL_error(L, "can't set non existing property"); abort(); } [[noreturn]] inline void errorCantSetReadOnlyProperty(lua_State* L) { luaL_error(L, "can't set read only property"); abort(); } +[[noreturn]] inline void errorDeadEvent(lua_State* L) { luaL_error(L, "dead event"); abort(); } [[noreturn]] inline void errorDeadMethod(lua_State* L) { luaL_error(L, "dead method"); abort(); } [[noreturn]] inline void errorDeadObject(lua_State* L) { luaL_error(L, "dead object"); abort(); } [[noreturn]] inline void errorExpectedNArgumentsGotN(lua_State* L, int expected, int got) { luaL_error(L, "expected %d arguments, got %d", expected, got); abort(); } +[[noreturn]] inline void errorExpectedNNArgumentsGotN(lua_State* L, int min, int max, int got) { luaL_error(L, "expected %d..%d arguments, got %d", min, max, got); abort(); } [[noreturn]] inline void errorException(lua_State* L, const std::exception& e) { luaL_error(L, "exception: %s", e.what()); abort(); } diff --git a/server/src/lua/event.cpp b/server/src/lua/event.cpp new file mode 100644 index 00000000..eb430dd6 --- /dev/null +++ b/server/src/lua/event.cpp @@ -0,0 +1,134 @@ +/** + * server/src/lua/event.cpp - Lua event wrapper + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "event.hpp" +#include "to.hpp" +#include "checkarguments.hpp" +#include "error.hpp" +#include "eventhandler.hpp" +#include "sandbox.hpp" +#include "../core/abstractevent.hpp" +#include "../core/object.hpp" + +namespace Lua { + +struct EventData +{ + ObjectPtrWeak object; + AbstractEvent& event; + + EventData(AbstractEvent& _event) : + object{_event.object().weak_from_this()}, + event{_event} + { + } +}; + +AbstractEvent& Event::check(lua_State* L, int index) +{ + EventData& data = **static_cast(luaL_checkudata(L, index, metaTableName)); + if(!data.object.expired()) + return data.event; + + errorDeadEvent(L); +} + +AbstractEvent* Event::test(lua_State* L, int index) +{ + EventData** data = static_cast(luaL_testudata(L, index, metaTableName)); + if(!data) + return nullptr; + else if(!(**data).object.expired()) + return &(**data).event; + + errorDeadEvent(L); +} + +void Event::push(lua_State* L, AbstractEvent& value) +{ + *static_cast(lua_newuserdata(L, sizeof(EventData*))) = new EventData(value); + luaL_getmetatable(L, metaTableName); + lua_setmetatable(L, -2); +} + +void Event::registerType(lua_State* L) +{ + luaL_newmetatable(L, metaTableName); + lua_pushcfunction(L, __index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, __gc); + lua_setfield(L, -2, "__gc"); + lua_pop(L, 1); +} + +int Event::__index(lua_State* L) +{ + auto& event = check(L, 1); + auto name = to(L, 2); + + if(name == "connect") + { + push(L, event); + lua_pushcclosure(L, connect, 1); + } + else if(name == "disconnect") + { + push(L, event); + lua_pushcclosure(L, disconnect, 1); + } + else + lua_pushnil(L); + + return 1; +} + +int Event::__gc(lua_State* L) +{ + delete *static_cast(lua_touserdata(L, 1)); + return 0; +} + +int Event::connect(lua_State* L) +{ + checkArguments(L, 1, 2); + + auto& event = check(L, lua_upvalueindex(1)); + auto handler = std::make_shared(event, L); + event.connect(handler); + lua_pushinteger(L, Sandbox::getStateData(L).registerEventHandler(handler)); + + return 1; +} + +int Event::disconnect(lua_State* L) +{ + checkArguments(L, 1); + + auto& event = check(L, lua_upvalueindex(1)); + auto handler = Sandbox::getStateData(L).getEventHandler(luaL_checkinteger(L, 1)); + + lua_pushboolean(L, handler && &handler->event() == &event && handler->disconnect()); + + return 1; +} + +} diff --git a/server/src/lua/event.hpp b/server/src/lua/event.hpp new file mode 100644 index 00000000..0b422ed2 --- /dev/null +++ b/server/src/lua/event.hpp @@ -0,0 +1,53 @@ +/** + * server/src/lua/event.hpp - Lua event wrapper + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_LUA_EVENT_HPP +#define TRAINTASTIC_SERVER_LUA_EVENT_HPP + +#include + +class AbstractEvent; + +namespace Lua { + +class Event +{ + private: + static int __index(lua_State* L); + static int __gc(lua_State* L); + static int connect(lua_State* L); + static int disconnect(lua_State* L); + + public: + static constexpr char const* metaTableName = "event"; + + static AbstractEvent& check(lua_State* L, int index); + static AbstractEvent* test(lua_State* L, int index); + + static void push(lua_State* L, AbstractEvent& value); + + static void registerType(lua_State* L); +}; + +} + +#endif diff --git a/server/src/lua/eventhandler.cpp b/server/src/lua/eventhandler.cpp new file mode 100644 index 00000000..c8a571a9 --- /dev/null +++ b/server/src/lua/eventhandler.cpp @@ -0,0 +1,128 @@ +/** + * server/src/lua/eventhandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "eventhandler.hpp" +#include "sandbox.hpp" +#include "push.hpp" +#include "../core/abstractevent.hpp" +#include "../core/object.hpp" + +namespace Lua { + +EventHandler::EventHandler(AbstractEvent& evt, lua_State* L) + : AbstractEventHandler(evt) + , m_L{L} + , m_function{LUA_NOREF} + , m_userData{LUA_NOREF} +{ + luaL_checktype(L, 1, LUA_TFUNCTION); + + // add function to registry: + lua_pushvalue(L, 1); + m_function = luaL_ref(m_L, LUA_REGISTRYINDEX); + + // add userdata to registry (if available): + if(!lua_isnoneornil(L, 2)) + { + lua_pushvalue(L, 2); + m_userData = luaL_ref(m_L, LUA_REGISTRYINDEX);; + } +} + +EventHandler::~EventHandler() +{ + release(); +} + +void EventHandler::execute(const Arguments& args) +{ + const auto argumentInfo = m_event.argumentInfo(); + assert(args.size() == argumentInfo.second); + + lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_function); + + const size_t nargs = args.size(); + for(size_t i = 0; i < nargs; i++) + { + const auto& arg = args[i]; + switch(argumentInfo.first[i].first) + { + case ValueType::Boolean: + push(m_L, std::get(arg)); + break; + + case ValueType::Enum: + pushEnum(m_L, argumentInfo.first[i].second.data(), std::get(arg)); + break; + + case ValueType::Integer: + push(m_L, std::get(arg)); + break; + + case ValueType::Float: + push(m_L, std::get(arg)); + break; + + case ValueType::String: + push(m_L, std::get(arg)); + break; + + case ValueType::Object: + push(m_L, std::get(arg)); + break; + + case ValueType::Set: + pushSet(m_L, argumentInfo.first[i].second.data(), std::get(arg)); + break; + + case ValueType::Invalid: + default: + assert(false); + lua_pushnil(m_L); + break; + } + } + + push(m_L, m_event.object().shared_from_this()); + lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_userData); + + Sandbox::pcall(m_L, args.size() + 2, 0, 0); +} + +bool EventHandler::disconnect() +{ + Sandbox::getStateData(m_L).unregisterEventHandler(std::dynamic_pointer_cast(shared_from_this())); + release(); + return AbstractEventHandler::disconnect(); +} + +void EventHandler::release() +{ + if(m_L) + { + luaL_unref(m_L, LUA_REGISTRYINDEX, m_function); + luaL_unref(m_L, LUA_REGISTRYINDEX, m_userData); + m_L = nullptr; + } +} + +} diff --git a/server/src/lua/eventhandler.hpp b/server/src/lua/eventhandler.hpp new file mode 100644 index 00000000..a086d6ae --- /dev/null +++ b/server/src/lua/eventhandler.hpp @@ -0,0 +1,51 @@ +/** + * server/src/lua/eventhandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_LUA_EVENTHANDLER_HPP +#define TRAINTASTIC_SERVER_LUA_EVENTHANDLER_HPP + +#include +#include "../core/abstracteventhandler.hpp" + +namespace Lua { + +class EventHandler final : public AbstractEventHandler +{ + private: + lua_State* m_L; + int m_function; + int m_userData; + + void release(); + + public: + EventHandler(AbstractEvent& _event, lua_State* L); + ~EventHandler() final; + + void execute(const Arguments& args) final; + + bool disconnect() final; +}; + +} + +#endif diff --git a/server/src/lua/log.cpp b/server/src/lua/log.cpp index c6a9efe5..79a5020d 100644 --- a/server/src/lua/log.cpp +++ b/server/src/lua/log.cpp @@ -26,6 +26,7 @@ #include "script.hpp" #include "object.hpp" #include "method.hpp" +#include "event.hpp" #include "../log/log.hpp" namespace Lua { @@ -79,6 +80,8 @@ int Log::log(lua_State* L, LogMessage code) message += object->getClassId(); else if(Method::test(L, i)) message += "method"; + else if(Event::test(L, i)) + message += "event"; else { lua_getglobal(L, "tostring"); diff --git a/server/src/lua/method.cpp b/server/src/lua/method.cpp index c4fda310..739ec830 100644 --- a/server/src/lua/method.cpp +++ b/server/src/lua/method.cpp @@ -91,7 +91,7 @@ int Method::__call(lua_State* L) if(lua_gettop(L) - 1 != argc) errorExpectedNArgumentsGotN(L, argc, lua_gettop(L) - 1); - std::vector args; + Arguments args; args.reserve(argc); diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index 62c2fde0..acd1985d 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -25,10 +25,12 @@ #include "check.hpp" #include "to.hpp" #include "method.hpp" +#include "event.hpp" #include "error.hpp" #include "../core/object.hpp" #include "../core/abstractproperty.hpp" #include "../core/abstractmethod.hpp" +#include "../core/abstractevent.hpp" namespace Lua { @@ -164,6 +166,13 @@ int Object::__index(lua_State* L) else lua_pushnil(L); } + else if(auto* event = dynamic_cast(item)) + { + if(event->isScriptable()) + Event::push(L, *event); + else + lua_pushnil(L); + } else { assert(false); // it must be a property or method diff --git a/server/src/lua/sandbox.cpp b/server/src/lua/sandbox.cpp index 9ba096bd..7fedd409 100644 --- a/server/src/lua/sandbox.cpp +++ b/server/src/lua/sandbox.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,6 +24,8 @@ #include "push.hpp" #include "object.hpp" #include "method.hpp" +#include "event.hpp" +#include "eventhandler.hpp" #include "log.hpp" #include "class.hpp" #include "to.hpp" @@ -167,6 +169,7 @@ SandboxPtr Sandbox::create(Script& script) Set::registerType(L); Object::registerType(L); Method::registerType(L); + Event::registerType(L); // setup sandbox: lua_newtable(L); @@ -300,4 +303,14 @@ int Sandbox::pcall(lua_State* L, int nargs, int nresults, int errfunc) return lua_pcall(L, nargs, nresults, errfunc); } +Sandbox::StateData::~StateData() +{ + while(!m_eventHandlers.empty()) + { + auto handler = m_eventHandlers.begin()->second; + m_eventHandlers.erase(m_eventHandlers.begin()); + handler->disconnect(); + } +} + } diff --git a/server/src/lua/sandbox.hpp b/server/src/lua/sandbox.hpp index 13e6ee56..9370d927 100644 --- a/server/src/lua/sandbox.hpp +++ b/server/src/lua/sandbox.hpp @@ -24,11 +24,15 @@ #define TRAINTASTIC_SERVER_LUA_SANDBOX_HPP #include +#include +#include +#include #include namespace Lua { class Script; +class EventHandler; using SandboxPtr = std::unique_ptr; @@ -44,17 +48,58 @@ class Sandbox { private: Script& m_script; + lua_Integer m_eventHandlerId; + std::map> m_eventHandlers; public: - StateData(Script& script) : - m_script{script} + StateData(Script& script) + : m_script{script} + , m_eventHandlerId{1} { } + ~StateData(); + inline Script& script() const { return m_script; } + + std::shared_ptr getEventHandler(lua_Integer id) const + { + auto it = m_eventHandlers.find(id); + if(it != m_eventHandlers.end()) + return it->second; + else + return std::shared_ptr(); + } + + inline lua_Integer registerEventHandler(std::shared_ptr handler) + { + while(m_eventHandlers.find(m_eventHandlerId) != m_eventHandlers.end()) + { + if(m_eventHandlerId == std::numeric_limits::max()) + m_eventHandlerId = 1; + else + m_eventHandlerId++; + } + const lua_Integer id = m_eventHandlerId; + m_eventHandlerId++; + m_eventHandlers.emplace(id, std::move(handler)); + return id; + } + + inline void unregisterEventHandler(const std::shared_ptr& handler) + { + auto it = std::find_if(m_eventHandlers.begin(), m_eventHandlers.end(), + [&handler](const auto& elem) + { + return elem.second == handler; + }); + + if(it != m_eventHandlers.end()) + m_eventHandlers.erase(it); + } }; static SandboxPtr create(Script& script); diff --git a/server/src/lua/script.cpp b/server/src/lua/script.cpp index 92e28c82..c5f83783 100644 --- a/server/src/lua/script.cpp +++ b/server/src/lua/script.cpp @@ -116,19 +116,6 @@ void Script::worldEvent(WorldState worldState, WorldEvent worldEvent) IdObject::worldEvent(worldState, worldEvent); updateEnabled(); - - if(m_sandbox) - { - lua_State* L = m_sandbox.get(); - if(Sandbox::getGlobal(L, "world_event") == LUA_TFUNCTION) - { - push(L, worldState); - push(L, worldEvent); - pcall(L, 2); - } - else - lua_pop(L, 1); - } } void Script::updateEnabled() From 0bbee528c1605a84f6341d18d1f9021d349a2fc1 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 24 Nov 2021 00:08:27 +0100 Subject: [PATCH 34/43] manual: added support for deeper page nesting --- .../traintasticmanualbuilder/htmlsinglepage.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/manual/traintasticmanualbuilder/htmlsinglepage.py b/manual/traintasticmanualbuilder/htmlsinglepage.py index c8598ac9..f8098d91 100644 --- a/manual/traintasticmanualbuilder/htmlsinglepage.py +++ b/manual/traintasticmanualbuilder/htmlsinglepage.py @@ -36,6 +36,15 @@ class HTMLSinglePageBuilder(HTMLBuilder): return html + def subpages(self, page, depth=1): + html = '' + if 'pages' in page: + for subpage in page['pages']: + subhtml = self._file_to_html(subpage) + subhtml = re.sub(r']*)>(.*?)', lambda m: '' + m.group(3) + '', subhtml) + html += subhtml + self.subpages(subpage, depth + 1) + return html + def build(self): self._output_copy_files([ 'css/pure-min.css', @@ -46,12 +55,7 @@ class HTMLSinglePageBuilder(HTMLBuilder): toc = {'preface': [], 'chapter': [], 'appendix': []} manual_html = '' for page in self._json: - page_html = self._file_to_html(page) - if 'pages' in page: - for subpage in page['pages']: - subhtml = self._file_to_html(subpage) - subhtml = re.sub(r']*)>(.*?)', lambda m: '' + m.group(3) + '', subhtml) - page_html += subhtml + page_html = self._file_to_html(page) + self.subpages(page) m = re.findall(r']*)>(.*?)', page_html) if m is not None: From b00c0d372cfcebade9567cab733b2c707e4aec94 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 24 Nov 2021 00:11:50 +0100 Subject: [PATCH 35/43] manual: add some Lua scripting documentation --- manual/traintasticmanual/en-us/lua.md | 3 + .../en-us/lua/enum/worldevent.md | 20 +++++++ .../en-us/lua/enum/worldscale.md | 13 ++++ manual/traintasticmanual/en-us/lua/enums.md | 4 ++ manual/traintasticmanual/en-us/lua/globals.md | 59 +++++++++++++++++++ .../traintasticmanual/en-us/lua/object/log.md | 25 ++++++++ .../en-us/lua/object/world.md | 23 ++++++++ manual/traintasticmanual/en-us/lua/objects.md | 4 ++ .../en-us/lua/set/worldstate.md | 14 +++++ manual/traintasticmanual/en-us/lua/sets.md | 3 + .../en-us/messages/critical.md | 2 +- .../traintasticmanual/en-us/messages/debug.md | 2 +- .../traintasticmanual/en-us/messages/error.md | 2 +- .../traintasticmanual/en-us/messages/fatal.md | 2 +- .../traintasticmanual/en-us/messages/info.md | 2 +- .../en-us/messages/notice.md | 2 +- .../en-us/messages/warning.md | 2 +- manual/traintasticmanual/en-us/scripting.md | 3 - .../traintasticmanual/traintasticmanual.json | 37 +++++++++++- 19 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 manual/traintasticmanual/en-us/lua.md create mode 100644 manual/traintasticmanual/en-us/lua/enum/worldevent.md create mode 100644 manual/traintasticmanual/en-us/lua/enum/worldscale.md create mode 100644 manual/traintasticmanual/en-us/lua/enums.md create mode 100644 manual/traintasticmanual/en-us/lua/globals.md create mode 100644 manual/traintasticmanual/en-us/lua/object/log.md create mode 100644 manual/traintasticmanual/en-us/lua/object/world.md create mode 100644 manual/traintasticmanual/en-us/lua/objects.md create mode 100644 manual/traintasticmanual/en-us/lua/set/worldstate.md create mode 100644 manual/traintasticmanual/en-us/lua/sets.md delete mode 100644 manual/traintasticmanual/en-us/scripting.md diff --git a/manual/traintasticmanual/en-us/lua.md b/manual/traintasticmanual/en-us/lua.md new file mode 100644 index 00000000..6bdf29a6 --- /dev/null +++ b/manual/traintasticmanual/en-us/lua.md @@ -0,0 +1,3 @@ +# Lua scripting {#lua} + +TODO diff --git a/manual/traintasticmanual/en-us/lua/enum/worldevent.md b/manual/traintasticmanual/en-us/lua/enum/worldevent.md new file mode 100644 index 00000000..61367b28 --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/enum/worldevent.md @@ -0,0 +1,20 @@ +# World event {#lua-enum-world-event} + +Global prefix: `enum.world_event` + +## Values + +| Value | Description | +| --------------- | ----------- | +| `EDIT_DISABLED` | | +| `EDIT_ENABLED` | | +| `OFFLINE` | | +| `ONLINE` | | +| `POWER_OFF` | | +| `POWER_ON` | | +| `STOP` | | +| `RUN` | | +| `UNMUTE` | | +| `MUTE` | | +| `NO_SMOKE` | | +| `SMOKE` | | diff --git a/manual/traintasticmanual/en-us/lua/enum/worldscale.md b/manual/traintasticmanual/en-us/lua/enum/worldscale.md new file mode 100644 index 00000000..123acbda --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/enum/worldscale.md @@ -0,0 +1,13 @@ +# World scale {#lua-enum-world-scale} + +Global prefix: `enum.world_scale` + +## Values + +| Value | Description | Ratio | +| -------- | ------------ | ----- | +| `H0` | H0 scale | 1:87 | +| `TT` | TT scale | 1:120 | +| `N` | N scale | 1:160 | +| `Z` | Z scale | 1:220 | +| `CUSTOM` | Custom scale | | diff --git a/manual/traintasticmanual/en-us/lua/enums.md b/manual/traintasticmanual/en-us/lua/enums.md new file mode 100644 index 00000000..7d5383d4 --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/enums.md @@ -0,0 +1,4 @@ +# Enums {#lua-enums} + +- [`world_event`](enum/worldevent.md) +- [`world_scale`](enum/worldscale.md) diff --git a/manual/traintasticmanual/en-us/lua/globals.md b/manual/traintasticmanual/en-us/lua/globals.md new file mode 100644 index 00000000..766ba39a --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/globals.md @@ -0,0 +1,59 @@ +# Globals {#lua-globals} + +TODO + + +## Constants + +`CODENAME` + +`LUA_VERSION` + +`VERSION` + +`VERSION_MAJOR` + +`VERSION_MINOR` + +`VERSION_PATCH` + + +## Functions + +`assert()` + +`get_class()` + +`ipairs()` + +`next()` + +`pairs()` + +`tonumber()` + +`tostring()` + +`type()` + + +## Objects + +`log` + +`world` + + +## ??? + +`class` + +`enum` + +`math` + +`set` + +`string` + +`table` diff --git a/manual/traintasticmanual/en-us/lua/object/log.md b/manual/traintasticmanual/en-us/lua/object/log.md new file mode 100644 index 00000000..e65feeed --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/object/log.md @@ -0,0 +1,25 @@ +# Log {#lua-object-log} + +## Properties + +*None.* + +## Methods + +`debug(...)` + +`info(...)` + +`notice(...)` + +`warning(...)` + +`error(...)` + +`critical(...)` + +`fatal(...)` + +## Events + +*None.* diff --git a/manual/traintasticmanual/en-us/lua/object/world.md b/manual/traintasticmanual/en-us/lua/object/world.md new file mode 100644 index 00000000..1bf1f544 --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/object/world.md @@ -0,0 +1,23 @@ +# World {#lua-object-world} + +## Properties + +`string uuid` + +`string name` + +`world_scale scale` + +`number scale_ratio` + +`world_state state` + +## Methods + +`power_off()` + +`stop()` + +## Events + +`on_event(world_state state, world_event event)` diff --git a/manual/traintasticmanual/en-us/lua/objects.md b/manual/traintasticmanual/en-us/lua/objects.md new file mode 100644 index 00000000..30fb3c9a --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/objects.md @@ -0,0 +1,4 @@ +# Objects {#lua-objects} + +- [`log`](object/log.md) +- [`world`](object/world.md) diff --git a/manual/traintasticmanual/en-us/lua/set/worldstate.md b/manual/traintasticmanual/en-us/lua/set/worldstate.md new file mode 100644 index 00000000..51d3a0af --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/set/worldstate.md @@ -0,0 +1,14 @@ +# World state {#lua-set-world-state} + +Global prefix: `set.world_state` + +## Values + +| Value | Description | +| ---------- | ----------- | +| `EDIT` | | +| `ONLINE` | | +| `POWER_ON` | | +| `RUN` | | +| `MUTE` | | +| `NO_SMOKE` | | diff --git a/manual/traintasticmanual/en-us/lua/sets.md b/manual/traintasticmanual/en-us/lua/sets.md new file mode 100644 index 00000000..f43a196a --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/sets.md @@ -0,0 +1,3 @@ +# Sets {#lua-sets} + +- [`world_state`](set/worldstate.md) diff --git a/manual/traintasticmanual/en-us/messages/critical.md b/manual/traintasticmanual/en-us/messages/critical.md index 3369e958..32e4970b 100644 --- a/manual/traintasticmanual/en-us/messages/critical.md +++ b/manual/traintasticmanual/en-us/messages/critical.md @@ -59,4 +59,4 @@ TODO ## C9999: *message* {#c9999} -Custom critical message generated by a [Lua script](../scripting.md). +Custom critical message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/messages/debug.md b/manual/traintasticmanual/en-us/messages/debug.md index 37ffc376..feaaac4a 100644 --- a/manual/traintasticmanual/en-us/messages/debug.md +++ b/manual/traintasticmanual/en-us/messages/debug.md @@ -55,4 +55,4 @@ TODO ## D9999: *message* {#d9999} -Custom debug message generated by a [Lua script](../scripting.md). +Custom debug message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/messages/error.md b/manual/traintasticmanual/en-us/messages/error.md index bde9267e..b48c3e7b 100644 --- a/manual/traintasticmanual/en-us/messages/error.md +++ b/manual/traintasticmanual/en-us/messages/error.md @@ -105,4 +105,4 @@ TODO ## E9999: *message* {#e9999} -Custom error message generated by a [Lua script](../scripting.md). +Custom error message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/messages/fatal.md b/manual/traintasticmanual/en-us/messages/fatal.md index ec7cb600..be955ca1 100644 --- a/manual/traintasticmanual/en-us/messages/fatal.md +++ b/manual/traintasticmanual/en-us/messages/fatal.md @@ -55,4 +55,4 @@ TODO ## F9999: *message* {#f9999} -Custom fatal message generated by a [Lua script](../scripting.md). +Custom fatal message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/messages/info.md b/manual/traintasticmanual/en-us/messages/info.md index bd8febbf..a3f4c973 100644 --- a/manual/traintasticmanual/en-us/messages/info.md +++ b/manual/traintasticmanual/en-us/messages/info.md @@ -35,4 +35,4 @@ TODO ## I9999: *message* {#i9999} -Custom info message generated by a [Lua script](../scripting.md). +Custom info message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/messages/notice.md b/manual/traintasticmanual/en-us/messages/notice.md index ef996a5b..6be6b63a 100644 --- a/manual/traintasticmanual/en-us/messages/notice.md +++ b/manual/traintasticmanual/en-us/messages/notice.md @@ -116,4 +116,4 @@ TODO ## N9999: *message* {#n9999} -Custom notice message generated by a [Lua script](../scripting.md). +Custom notice message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/messages/warning.md b/manual/traintasticmanual/en-us/messages/warning.md index 50aa3206..79df75e9 100644 --- a/manual/traintasticmanual/en-us/messages/warning.md +++ b/manual/traintasticmanual/en-us/messages/warning.md @@ -35,4 +35,4 @@ If not, remapping decoder functions or using a different command station is the ## W9999: *message* {#w9999} -Custom warning message generated by a [Lua script](../scripting.md). +Custom warning message generated by a [Lua script](../lua.md). diff --git a/manual/traintasticmanual/en-us/scripting.md b/manual/traintasticmanual/en-us/scripting.md deleted file mode 100644 index 41d0d33c..00000000 --- a/manual/traintasticmanual/en-us/scripting.md +++ /dev/null @@ -1,3 +0,0 @@ -# Scripting {#scripting} - -TODO diff --git a/manual/traintasticmanual/traintasticmanual.json b/manual/traintasticmanual/traintasticmanual.json index 21af4e94..291b07b8 100644 --- a/manual/traintasticmanual/traintasticmanual.json +++ b/manual/traintasticmanual/traintasticmanual.json @@ -60,7 +60,42 @@ }, { "type": "chapter", - "markdown": "scripting.md" + "markdown": "lua.md", + "pages": [ + { + "markdown": "lua/globals.md" + }, + { + "markdown": "lua/enums.md", + "pages": [ + { + "markdown": "lua/enum/worldevent.md" + }, + { + "markdown": "lua/enum/worldscale.md" + } + ] + }, + { + "markdown": "lua/sets.md", + "pages": [ + { + "markdown": "lua/set/worldstate.md" + } + ] + }, + { + "markdown": "lua/objects.md", + "pages": [ + { + "markdown": "lua/object/log.md" + }, + { + "markdown": "lua/object/world.md" + } + ] + } + ] }, { "type": "chapter", From 393e6585d95dedb4a856cf8f1978a0acbb6196fb Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 24 Nov 2021 09:51:04 +0100 Subject: [PATCH 36/43] fix: added missing include --- server/src/lua/class.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/lua/class.hpp b/server/src/lua/class.hpp index 3c487b08..ccccab81 100644 --- a/server/src/lua/class.hpp +++ b/server/src/lua/class.hpp @@ -23,6 +23,7 @@ #ifndef TRAINTASTIC_SERVER_LUA_CLASS_HPP #define TRAINTASTIC_SERVER_LUA_CLASS_HPP +#include #include #include "../core/objectptr.hpp" From 546c87e94d72a06a9f6ddd6896dccc6aadeab7a0 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 27 Nov 2021 11:05:04 +0100 Subject: [PATCH 37/43] lua: added support for object lists --- server/src/core/abstractobjectlist.cpp | 5 +- server/src/core/abstractobjectlist.hpp | 5 ++ server/src/core/objectlist.hpp | 7 +-- server/src/lua/object.cpp | 75 ++++++++++++++++++++++++-- server/src/lua/object.hpp | 6 +++ 5 files changed, 87 insertions(+), 11 deletions(-) diff --git a/server/src/core/abstractobjectlist.cpp b/server/src/core/abstractobjectlist.cpp index dc798b41..6c85b6c5 100644 --- a/server/src/core/abstractobjectlist.cpp +++ b/server/src/core/abstractobjectlist.cpp @@ -24,8 +24,9 @@ #include "../core/idobject.hpp" #include "../world/worldloader.hpp" -AbstractObjectList::AbstractObjectList(Object& _parent, const std::string& parentPropertyName) : - SubObject{_parent, parentPropertyName} +AbstractObjectList::AbstractObjectList(Object& _parent, const std::string& parentPropertyName) + : SubObject{_parent, parentPropertyName} + , length{this, "length", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::NoScript} { } diff --git a/server/src/core/abstractobjectlist.hpp b/server/src/core/abstractobjectlist.hpp index 6df4b3eb..10e89213 100644 --- a/server/src/core/abstractobjectlist.hpp +++ b/server/src/core/abstractobjectlist.hpp @@ -25,6 +25,7 @@ #include "subobject.hpp" #include "table.hpp" +#include "property.hpp" class WorldSaver; @@ -40,7 +41,11 @@ class AbstractObjectList : public SubObject, public Table virtual void setItems(const std::vector& items) = 0; public: + Property length; + AbstractObjectList(Object& _parent, const std::string& parentPropertyName); + + virtual ObjectPtr getObject(uint32_t index) = 0; }; #endif diff --git a/server/src/core/objectlist.hpp b/server/src/core/objectlist.hpp index 986ff39a..5a697f0c 100644 --- a/server/src/core/objectlist.hpp +++ b/server/src/core/objectlist.hpp @@ -76,18 +76,15 @@ class ObjectList : public AbstractObjectList } public: - Property length; - ObjectList(Object& _parent, const std::string& parentPropertyName) : - AbstractObjectList{_parent, parentPropertyName}, - length{this, "length", 0, PropertyFlags::ReadOnly} + AbstractObjectList{_parent, parentPropertyName} { } inline const_iterator begin() const noexcept { return m_items.begin(); } inline const_iterator end() const noexcept { return m_items.end(); } - ObjectPtr getObject(uint32_t index) + ObjectPtr getObject(uint32_t index) final { assert(index < m_items.size()); return std::static_pointer_cast(m_items[index]); diff --git a/server/src/lua/object.cpp b/server/src/lua/object.cpp index acd1985d..653e327b 100644 --- a/server/src/lua/object.cpp +++ b/server/src/lua/object.cpp @@ -36,24 +36,50 @@ namespace Lua { ObjectPtr Object::check(lua_State* L, int index) { - ObjectPtrWeak& data = **static_cast(luaL_checkudata(L, index, metaTableName)); - if(ObjectPtr object = data.lock()) + ObjectPtrWeak** data = static_cast(luaL_testudata(L, index, metaTableNameList)); + if(!data) + data = static_cast(luaL_checkudata(L, index, metaTableName)); + + if(ObjectPtr object = (**data).lock()) return object; else errorDeadObject(L); } +std::shared_ptr Object::checkList(lua_State* L, int index) +{ + ObjectPtrWeak& data = **static_cast(luaL_checkudata(L, index, metaTableNameList)); + if(ObjectPtr object = data.lock()) + return std::static_pointer_cast(object); + else + errorDeadObject(L); +} + ObjectPtr Object::test(lua_State* L, int index) { ObjectPtrWeak** data = static_cast(luaL_testudata(L, index, metaTableName)); if(!data) - return ObjectPtr(); + data = static_cast(luaL_testudata(L, index, metaTableNameList)); + + if(!data) + return {}; else if(ObjectPtr object = (**data).lock()) return object; else errorDeadObject(L); } +std::shared_ptr Object::testList(lua_State* L, int index) +{ + ObjectPtrWeak** data = static_cast(luaL_testudata(L, index, metaTableNameList)); + if(!data) + return {}; + else if(ObjectPtr object = (**data).lock()) + return std::static_pointer_cast(object); + else + errorDeadObject(L); +} + void Object::push(lua_State* L, const ObjectPtr& value) { if(value) @@ -64,7 +90,10 @@ void Object::push(lua_State* L, const ObjectPtr& value) { lua_pop(L, 1); // remove nil *static_cast(lua_newuserdata(L, sizeof(ObjectPtrWeak*))) = new ObjectPtrWeak(value); - luaL_setmetatable(L, metaTableName); + if(dynamic_cast(value.get())) + luaL_setmetatable(L, metaTableNameList); + else + luaL_setmetatable(L, metaTableName); lua_pushvalue(L, -1); // copy userdata on stack lua_rawsetp(L, -3, value.get()); // add object to table } @@ -87,6 +116,18 @@ void Object::registerType(lua_State* L) lua_setfield(L, -2, "__newindex"); lua_pop(L, 1); + // metatable for object list userdata: + luaL_newmetatable(L, metaTableNameList); + lua_pushcfunction(L, __gc); + lua_setfield(L, -2, "__gc"); + lua_pushcfunction(L, __index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, __newindex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, __len); + lua_setfield(L, -2, "__len"); + lua_pop(L, 1); + // weak table for object userdata: lua_newtable(L); lua_newtable(L); // metatable @@ -106,6 +147,23 @@ int Object::__gc(lua_State* L) int Object::__index(lua_State* L) { ObjectPtr object{check(L, 1)}; + + // handle list[index]: + { + lua_Integer index; + if(to(L, 2, index)) + { + if(auto list = std::dynamic_pointer_cast(object)) + { + if(index >= 1 && index <= list->length) + push(L, list->getObject(static_cast(index - 1))); + else + lua_pushnil(L); + return 1; + } + } + } + std::string_view name{to(L, 2)}; if(InterfaceItem* item = object->getItem(name)) @@ -234,4 +292,13 @@ int Object::__newindex(lua_State* L) errorCantSetNonExistingProperty(L); } +int Object::__len(lua_State* L) +{ + auto list{checkList(L, 1)}; + + Lua::push(L, list->length.value()); + + return 1; +} + } diff --git a/server/src/lua/object.hpp b/server/src/lua/object.hpp index ac67cf67..df96a185 100644 --- a/server/src/lua/object.hpp +++ b/server/src/lua/object.hpp @@ -26,6 +26,7 @@ #include #include #include "../core/objectptr.hpp" +#include "../core/abstractobjectlist.hpp" namespace Lua { @@ -35,12 +36,17 @@ class Object static int __gc(lua_State* L); static int __index(lua_State* L); static int __newindex(lua_State* L); + static int __len(lua_State* L); public: static constexpr char const* metaTableName = "object"; + static constexpr char const* metaTableNameList = "object_list"; static ObjectPtr check(lua_State* L, int index); + static std::shared_ptr checkList(lua_State* L, int index); + static ObjectPtr test(lua_State* L, int index); + static std::shared_ptr testList(lua_State* L, int index); static void push(lua_State* L, const ObjectPtr& value); From 34f01a334db59872a795599b10d7b088f0e8e55a Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 27 Nov 2021 11:26:46 +0100 Subject: [PATCH 38/43] manual: replaced pycmarkgfm by cmarkgfm which is available on windows --- README.md | 2 +- manual/traintasticmanualbuilder/html.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3dfbb04d..e772f2b8 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The project goal is to develop open source software that can control everything - liblua5.3 (Linux only) - Manual: - Python 3.6+ (older versions untested) - - pycmarkgfm (`pip3 install pycmarkgfm`) + - cmarkgfm (`pip3 install cmarkgfm`) Note: When cloning the source from git, git-lfs is required. diff --git a/manual/traintasticmanualbuilder/html.py b/manual/traintasticmanualbuilder/html.py index a09d0403..1e91c2e5 100644 --- a/manual/traintasticmanualbuilder/html.py +++ b/manual/traintasticmanualbuilder/html.py @@ -1,7 +1,7 @@ import os import re import codecs -import pycmarkgfm # pip3 install pycmarkgfm +import cmarkgfm # pip3 install cmarkgfm from .builder import Builder @@ -10,7 +10,7 @@ class HTMLBuilder(Builder): def _file_to_html(self, page): with codecs.open(os.path.join(self._language_dir, page['markdown']), 'r', 'utf-8') as md: - html = pycmarkgfm.gfm_to_html(md.read()) + html = cmarkgfm.github_flavored_markdown_to_html(md.read()) # parse id html = re.sub(r']*)>(.*) {#([a-z0-9-]+)}', r'\3', html) From 47bb794a824dc66012e54d95d48e9e2c6d601416 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 27 Nov 2021 11:40:14 +0100 Subject: [PATCH 39/43] lua: added script access for board, train and rail vehicle --- manual/traintasticmanual/en-us/lua/object/world.md | 8 +++++++- manual/traintasticmanual/en-us/lua/objects.md | 5 +++-- server/src/board/board.cpp | 2 +- server/src/core/idobject.cpp | 2 +- server/src/train/train.cpp | 2 +- server/src/vehicle/vehicle.cpp | 2 +- server/src/world/world.cpp | 6 +++--- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/manual/traintasticmanual/en-us/lua/object/world.md b/manual/traintasticmanual/en-us/lua/object/world.md index 1bf1f544..3a1e02ec 100644 --- a/manual/traintasticmanual/en-us/lua/object/world.md +++ b/manual/traintasticmanual/en-us/lua/object/world.md @@ -2,16 +2,22 @@ ## Properties -`string uuid` +`object_list boards` `string name` +`object_list rail_vehicles` + `world_scale scale` `number scale_ratio` `world_state state` +`object_list trains` + +`string uuid` + ## Methods `power_off()` diff --git a/manual/traintasticmanual/en-us/lua/objects.md b/manual/traintasticmanual/en-us/lua/objects.md index 30fb3c9a..2e775f57 100644 --- a/manual/traintasticmanual/en-us/lua/objects.md +++ b/manual/traintasticmanual/en-us/lua/objects.md @@ -1,4 +1,5 @@ # Objects {#lua-objects} -- [`log`](object/log.md) -- [`world`](object/world.md) +- [Board](object/board.md) +- [Log](object/log.md) +- [World](object/world.md) diff --git a/server/src/board/board.cpp b/server/src/board/board.cpp index d4bc5238..8441dc0a 100644 --- a/server/src/board/board.cpp +++ b/server/src/board/board.cpp @@ -31,7 +31,7 @@ Board::Board(const std::weak_ptr& world, std::string_view _id) : IdObject(world, _id), - name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store}, + name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, left{this, "left", 0, PropertyFlags::ReadOnly | PropertyFlags::Store}, top{this, "top", 0, PropertyFlags::ReadOnly | PropertyFlags::Store}, right{this, "right", 0, PropertyFlags::ReadOnly | PropertyFlags::Store}, diff --git a/server/src/core/idobject.cpp b/server/src/core/idobject.cpp index 4cee6655..143bf746 100644 --- a/server/src/core/idobject.cpp +++ b/server/src/core/idobject.cpp @@ -27,7 +27,7 @@ IdObject::IdObject(const std::weak_ptr& world, std::string_view _id) : Object{}, m_world{world}, - id{this, "id", std::string(_id.data(), _id.size()), PropertyFlags::ReadWrite | PropertyFlags::Store, + id{this, "id", std::string(_id.data(), _id.size()), PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](const std::string& value) { idChanged(value); diff --git a/server/src/train/train.cpp b/server/src/train/train.cpp index 8a62f276..038c4a66 100644 --- a/server/src/train/train.cpp +++ b/server/src/train/train.cpp @@ -28,7 +28,7 @@ Train::Train(const std::weak_ptr& world, std::string_view _id) : IdObject(world, _id), - name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, + name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, lob{*this, "lob", 0, LengthUnit::MilliMeter, PropertyFlags::ReadOnly | PropertyFlags::Store}, direction{this, "direction", Direction::Forward, PropertyFlags::ReadWrite | PropertyFlags::StoreState}, speed{*this, "speed", 0, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadOnly | PropertyFlags::NoStore}, diff --git a/server/src/vehicle/vehicle.cpp b/server/src/vehicle/vehicle.cpp index 8d7cf3ac..37eec566 100644 --- a/server/src/vehicle/vehicle.cpp +++ b/server/src/vehicle/vehicle.cpp @@ -27,7 +27,7 @@ Vehicle::Vehicle(const std::weak_ptr& world, std::string_view _id) : IdObject(world, _id), - name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, + name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store} { auto w = world.lock(); diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index 1fd4f30b..3325ef6b 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -76,10 +76,10 @@ World::World(Private) : controllers{this, "controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, loconets{this, "loconets", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, xpressnets{this, "xpressnets", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, - boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, + boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}, clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, - trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, - railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, + trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}, + railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}, #ifndef DISABLE_LUA_SCRIPTING luaScripts{this, "lua_scripts", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, #endif From 75fdf20c1151e4a57cc82808405635d0b88da260 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 28 Nov 2021 23:33:57 +0100 Subject: [PATCH 40/43] CI: pycmarkgfm -> cmarkgfm --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 228d223d..2640827e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -376,7 +376,7 @@ jobs: run: git lfs checkout - name: Install python packages - run: sudo pip3 install pycmarkgfm + run: sudo pip3 install cmarkgfm - name: Build manual working-directory: ${{github.workspace}}/manual From 3f811e9176bc48fae9ad147320cac7d4b3b7a00b Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 25 Dec 2021 23:30:06 +0100 Subject: [PATCH 41/43] manual: fixed build issues --- .../en-us/lua/object/board.md | 15 +++++++++++ .../traintasticmanual/traintasticmanual.json | 26 ++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 manual/traintasticmanual/en-us/lua/object/board.md diff --git a/manual/traintasticmanual/en-us/lua/object/board.md b/manual/traintasticmanual/en-us/lua/object/board.md new file mode 100644 index 00000000..f7660195 --- /dev/null +++ b/manual/traintasticmanual/en-us/lua/object/board.md @@ -0,0 +1,15 @@ +# Board {#lua-object-board} + +## Properties + +`string id` + +`string name` + +## Methods + +*None.* + +## Events + +*None.* diff --git a/manual/traintasticmanual/traintasticmanual.json b/manual/traintasticmanual/traintasticmanual.json index 291b07b8..b2c3c136 100644 --- a/manual/traintasticmanual/traintasticmanual.json +++ b/manual/traintasticmanual/traintasticmanual.json @@ -61,37 +61,51 @@ { "type": "chapter", "markdown": "lua.md", + "code": "lua", "pages": [ { - "markdown": "lua/globals.md" + "markdown": "lua/globals.md", + "code": "lua" }, { "markdown": "lua/enums.md", + "code": "lua", "pages": [ { - "markdown": "lua/enum/worldevent.md" + "markdown": "lua/enum/worldevent.md", + "code": "lua" }, { - "markdown": "lua/enum/worldscale.md" + "markdown": "lua/enum/worldscale.md", + "code": "lua" } ] }, { "markdown": "lua/sets.md", + "code": "lua", "pages": [ { - "markdown": "lua/set/worldstate.md" + "markdown": "lua/set/worldstate.md", + "code": "lua" } ] }, { "markdown": "lua/objects.md", + "code": "lua", "pages": [ { - "markdown": "lua/object/log.md" + "markdown": "lua/object/board.md", + "code": "lua" }, { - "markdown": "lua/object/world.md" + "markdown": "lua/object/log.md", + "code": "lua" + }, + { + "markdown": "lua/object/world.md", + "code": "lua" } ] } From cfe5b90673a821e6677b6979b1caa83282ddcfd7 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 25 Dec 2021 23:31:13 +0100 Subject: [PATCH 42/43] manual: added some lua syntax highlighting --- manual/traintasticmanual/css/traintasticmanual.css | 12 ++++++++++++ manual/traintasticmanualbuilder/html.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/manual/traintasticmanual/css/traintasticmanual.css b/manual/traintasticmanual/css/traintasticmanual.css index 037a4c15..75c379f3 100644 --- a/manual/traintasticmanual/css/traintasticmanual.css +++ b/manual/traintasticmanual/css/traintasticmanual.css @@ -169,6 +169,18 @@ font-size: 80%; } +/** Lua **********************************************************************/ + +code.lua span.const +{ + color: #4e004e; +} + +code.lua span.keyword +{ + color: #000080; +} + /** MEDIA BREAKPOINTS ********************************************************/ @media screen and (max-width: 991px) diff --git a/manual/traintasticmanualbuilder/html.py b/manual/traintasticmanualbuilder/html.py index 1e91c2e5..536a8b9d 100644 --- a/manual/traintasticmanualbuilder/html.py +++ b/manual/traintasticmanualbuilder/html.py @@ -21,6 +21,9 @@ class HTMLBuilder(Builder): # set target="_blank" for external links: html = re.sub(r']+href="http(s|)://)', r'(.+)', self._highlight_lua, html) + # change img title attribute to figcaption html = re.sub(r'(]+)title="([^">]*)"([^>]*>)', lambda m: @@ -30,3 +33,9 @@ class HTMLBuilder(Builder): html) return html + + def _highlight_lua(self, m): + code = m.group(1) + code = re.sub(r'\b([A-Z_][A-Z0-9_]*)\b', r'\1', code) # CONSTANTS + code = re.sub(r'\b(and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b', r'\1', code) # keywords + return '' + code + '' From f86057d96323a1c5f72b0c66e8547495c7f58a09 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 25 Dec 2021 23:43:30 +0100 Subject: [PATCH 43/43] manual: added lua globals descriptions --- manual/traintasticmanual/en-us/lua/globals.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manual/traintasticmanual/en-us/lua/globals.md b/manual/traintasticmanual/en-us/lua/globals.md index 766ba39a..2828a5f4 100644 --- a/manual/traintasticmanual/en-us/lua/globals.md +++ b/manual/traintasticmanual/en-us/lua/globals.md @@ -5,17 +5,17 @@ TODO ## Constants -`CODENAME` +`CODENAME` - Traintastic release name or development branch, e.g. `"master"`. -`LUA_VERSION` +`LUA_VERSION` - Lua version and copyright, e.g. `"Lua 5.3.3 Copyright (C) 1994-2016 Lua.org, PUC-Rio"` -`VERSION` +`VERSION` - Traintastic version, e.g. `"0.1.0"` -`VERSION_MAJOR` +`VERSION_MAJOR` - Traintastic major version, e.g. `0` -`VERSION_MINOR` +`VERSION_MINOR` - Traintastic minor version, e.g. `1` -`VERSION_PATCH` +`VERSION_PATCH` - Traintastic patch level, e.g. `0` ## Functions