Merge branch 'master' into interface-controller

Dieser Commit ist enthalten in:
Reinder Feenstra 2022-01-03 00:59:50 +01:00
Commit 11d4ebcd85
112 geänderte Dateien mit 2411 neuen und 553 gelöschten Zeilen

Datei anzeigen

@ -386,7 +386,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

Datei anzeigen

@ -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.

Datei anzeigen

@ -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();
}
@ -345,12 +347,15 @@ 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));
break;
case MouseMoveAction::None:
break;
}
}
}
@ -374,8 +379,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 +394,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 +421,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();
@ -531,9 +537,30 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
}
}
break;
case MouseMoveAction::None:
break;
}
}
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;

Datei anzeigen

@ -32,6 +32,7 @@
#include <traintastic/enum/signalaspect.hpp>
#include <traintastic/enum/tristate.hpp>
#include <traintastic/enum/turnoutposition.hpp>
#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
@ -88,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<SensorState> getBlockSensorStates(const TileLocation& l) const;
@ -105,10 +110,11 @@ class BoardAreaWidget : public QWidget
void paintEvent(QPaintEvent* event) final;
protected slots:
void settingsChanged();
void updateMinimumSize();
public:
static constexpr int zoomLevelMin = 0;
static constexpr int zoomLevelMin = -2;
static constexpr int zoomLevelMax = 15;
BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr);
@ -116,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<float>(getTileSize()) / getTileSize(0); }
void setMouseMoveAction(MouseMoveAction action);
void setMouseMoveTileId(TileId id);

Datei anzeigen

@ -0,0 +1,53 @@
/**
* 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 = {
/*.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},
};

Datei anzeigen

@ -0,0 +1,47 @@
/**
* 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 <QColor>
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

Datei anzeigen

@ -57,7 +57,7 @@ const std::array<TileInfo, 30> 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},
@ -97,6 +97,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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}
@ -107,7 +108,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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 +135,23 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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 +191,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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 +235,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
m_addActions.append(action);
if(auto* tb = dynamic_cast<QToolButton*>(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 +252,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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 +270,15 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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 +304,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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)
@ -323,6 +326,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> 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");
@ -382,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)
@ -408,18 +413,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 +454,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 +470,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
h >= 1 && h <= std::numeric_limits<uint8_t>::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 +482,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 +492,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,26 +522,29 @@ 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<size_t>(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);
}
}
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);
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -24,14 +24,20 @@
#include <cmath>
#include <QtMath>
#include <QPainterPath>
#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);

Datei anzeigen

@ -34,38 +34,34 @@
#include <traintastic/enum/tristate.hpp>
#include <traintastic/enum/turnoutposition.hpp>
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<SensorState> 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);

Datei anzeigen

@ -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<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode)));
});
}
else

Datei anzeigen

@ -72,7 +72,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> 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> connection, QWidget
delete spinner;
}
else
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode)));
});
}
else

Datei anzeigen

@ -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;

Datei anzeigen

@ -72,10 +72,10 @@ class AbstractProperty : public BaseProperty
return setValueInt64(static_cast<int64_t>(value));
}
[[nodiscard]] virtual int setValueBool(bool value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueInt64(int64_t value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueDouble(double value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueString(const QString& value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueBool(bool value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueInt64(int64_t value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueDouble(double value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
[[nodiscard]] virtual int setValueString(const QString& value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
signals:
void valueChanged();

Datei anzeigen

@ -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<typename T>
T getEnum(int index) const

Datei anzeigen

@ -24,7 +24,7 @@
#include "connection.hpp"
#include "callmethod.hpp"
Board::Board(std::shared_ptr<Connection> connection, Handle handle, const QString& classId) :
Board::Board(std::shared_ptr<Connection> connection, Handle handle) :
Object(std::move(connection), handle, classId),
m_getTileDataRequestId{Connection::invalidRequestId}
{

Datei anzeigen

@ -56,7 +56,7 @@ class Board final : public Object
public:
inline static const QString classId = QStringLiteral("board");
Board(std::shared_ptr<Connection> connection, Handle handle, const QString& classId);
Board(std::shared_ptr<Connection> connection, Handle handle);
~Board() final;
void getTileData();

Datei anzeigen

@ -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<AttributeName>();
const ValueType type = message.read<ValueType>();
const ValueType valueType = message.read<ValueType>();
switch(message.read<AttributeType>())
{
case AttributeType::Value:
{
QVariant value;
switch(type)
switch(valueType)
{
case ValueType::Boolean:
value = message.read<bool>();
@ -632,7 +632,7 @@ ObjectPtr Connection::readObject(const Message& message)
case AttributeType::Values:
{
const int length = message.read<int>(); // read uint32_t as int, Qt uses int for length
QList<QVariant> values = readArray(message, type, length);
QList<QVariant> 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> message)
void Connection::socketConnected()
{
std::unique_ptr<Message> request{Message::newRequest(Message::Command::Login)};
request->write(m_username.toUtf8());
request->write(m_password);
send(request,
[this](const std::shared_ptr<Message> message)
std::unique_ptr<Message> loginRequest{Message::newRequest(Message::Command::Login)};
loginRequest->write(m_username.toUtf8());
loginRequest->write(m_password);
send(loginRequest,
[this](const std::shared_ptr<Message> loginResponse)
{
if(message && message->isResponse() && !message->isError())
if(loginResponse && loginResponse->isResponse() && !loginResponse->isError())
{
std::unique_ptr<Message> request{Message::newRequest(Message::Command::NewSession)};
send(request,
[this](const std::shared_ptr<Message> message)
std::unique_ptr<Message> newSessionRequest{Message::newRequest(Message::Command::NewSession)};
send(newSessionRequest,
[this](const std::shared_ptr<Message> 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<ObjectProperty*>(m_traintastic->getProperty("world"));
connect(m_worldProperty, &ObjectProperty::valueChanged, this,
[this]()

Datei anzeigen

@ -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();

Datei anzeigen

@ -122,7 +122,7 @@ void ServerLogTableModel::processMessage(const Message& message)
log.code = message.read<LogMessage>();
log.message = Locale::tr("message:" + toCodeString(log.code));
const int argc = message.read<uint8_t>();
for(int i = 0; i < argc; i++)
for(int j = 0; j < argc; j++)
log.message = log.message.arg(QString::fromUtf8(message.read<QByteArray>()));
m_logs.append(log);
}

Datei anzeigen

@ -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
}

Datei anzeigen

@ -23,14 +23,25 @@
#ifndef TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
#define TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
#include <array>
#include "settingsbase.hpp"
#include "setting.hpp"
#include <traintastic/enum/enum.hpp>
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 +53,23 @@ class BoardSettings : public SettingsBase
return settings;
}
Setting<ColorScheme> colorScheme;
Setting<bool> turnoutDrawState;
Setting<bool> 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<BoardSettings::ColorScheme>
{
static constexpr std::array<BoardSettings::ColorScheme, 2> values = {BoardSettings::ColorScheme::Dark, BoardSettings::ColorScheme::Light};
};
#endif

Datei anzeigen

@ -28,6 +28,8 @@ BoardSettingsWidget::BoardSettingsWidget(QWidget* parent)
{
BoardSettings& s = BoardSettings::instance();
addSetting(s.colorScheme);
addSetting(s.turnoutDrawState);
addSetting(s.showBlockSensorStates);
done();

Datei anzeigen

@ -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();
}

Datei anzeigen

@ -25,6 +25,12 @@
#include "settingsbase.hpp"
template<class T>
struct SettingEnum
{
static_assert(sizeof(T) != sizeof(T));
};
template<class T>
class Setting
{

Datei anzeigen

@ -39,16 +39,30 @@ class SettingsBase : public QObject
template<class T>
inline T get(const QString& key, const T& defaultValue) const
{
return qvariant_cast<T>(m_settings.value(key, defaultValue));
if constexpr(std::is_enum_v<T>)
return static_cast<T>(m_settings.value(key, static_cast<uint>(defaultValue)).toUInt());
else
return qvariant_cast<T>(m_settings.value(key, defaultValue));
}
template<class T>
inline void set(const QString& key, const T& value)
{
if(m_settings.value(key) != value)
if constexpr(std::is_enum_v<T>)
{
m_settings.setValue(key, value);
emit changed();
if(m_settings.value(key) != static_cast<uint>(value))
{
m_settings.setValue(key, static_cast<uint>(value));
emit changed();
}
}
else
{
if(m_settings.value(key) != value)
{
m_settings.setValue(key, value);
emit changed();
}
}
}

Datei anzeigen

@ -24,7 +24,10 @@
#define TRAINTASTIC_CLIENT_SETTINGS_SETTINGSBASEWIDGET_HPP
#include <QScrollArea>
#include <QComboBox>
#include <traintastic/enum/enum.hpp>
#include "setting.hpp"
#include "../utils/enum.hpp"
class SettingsBaseWidget : public QScrollArea
{
@ -40,11 +43,31 @@ class SettingsBaseWidget : public QScrollArea
void addSettingOnOff(Setting<bool>& setting);
void addSettingDir(Setting<QString>& setting);
template<class T>
void addSettingEnumDropdown(Setting<T>& setting)
{
QComboBox* cb = new QComboBox(widget());
for(auto value : SettingEnum<T>::values)
{
cb->addItem(translateEnum(EnumName<T>::value, static_cast<quint64>(value)), static_cast<uint>(value));
if(setting.value() == value)
cb->setCurrentIndex(cb->count() - 1);
}
connect(cb, QOverload<int>::of(&QComboBox::currentIndexChanged),
[&setting, cb](int /*index*/)
{
setting.setValue(static_cast<T>(cb->currentData().toUInt()));
});
add(setting.name(), cb);
}
template<class T>
inline void addSetting(Setting<T>& setting)
{
if constexpr(std::is_same_v<T, bool>)
addSettingOnOff(setting);
else if constexpr(std::is_enum_v<T>)
addSettingEnumDropdown(setting);
else
static_assert(sizeof(T) != sizeof(T));
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021 Reinder Feenstra
* Copyright (C) 2019-2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -42,6 +42,7 @@
#include <traintastic/enum/xpressnetcommandstation.hpp>
#include <traintastic/enum/xpressnetinterfacetype.hpp>
#include <traintastic/enum/xpressnetserialinterfacetype.hpp>
#include "../settings/boardsettings.hpp"
#define GET_ENUM_VALUES(_type) \
if(enumName == EnumName<_type>::value) \
@ -87,6 +88,7 @@ QString translateEnum(const QString& enumName, qint64 value)
TRANSLATE_ENUM(XpressNetCommandStation)
TRANSLATE_ENUM(XpressNetInterfaceType)
TRANSLATE_ENUM(XpressNetSerialInterfaceType)
TRANSLATE_ENUM(BoardSettings::ColorScheme)
return enumName + "@" + QString::number(value);
}

Datei anzeigen

@ -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()))

Datei anzeigen

@ -96,7 +96,7 @@ void ObjectEditWidget::buildForm()
ObjectProperty* property = static_cast<ObjectProperty*>(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;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021 Reinder Feenstra
* Copyright (C) 2019-2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -51,9 +51,9 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
QWidget(parent),
m_requestIdInputMonitor{Connection::invalidRequestId},
m_requestIdOutputKeyboard{Connection::invalidRequestId},
m_buttonAdd{nullptr},
m_object{object},
m_toolbar{new QToolBar()},
m_buttonAdd{nullptr},
m_actionAdd{nullptr},
m_actionEdit{nullptr},
m_actionDelete{nullptr},
@ -106,12 +106,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
});

Datei anzeigen

@ -21,6 +21,7 @@
*/
#include "propertycombobox.hpp"
#include <cassert>
#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;
}
}
}

Datei anzeigen

@ -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(?=\\.)"),

Datei anzeigen

@ -91,7 +91,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
});
}
if(m_emergencyStop = m_object->getProperty("emergency_stop"))
if((m_emergencyStop = m_object->getProperty("emergency_stop")))
{
m_speedoMeter->setEStop(m_emergencyStop->toBool());
connect(m_emergencyStop, &AbstractProperty::valueChangedBool, m_speedoMeter, &SpeedoMeterWidget::setEStop);
@ -100,7 +100,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
if(auto* p = dynamic_cast<ObjectProperty*>(m_object->getProperty("functions")))
{
m_functionsRequestId = m_object->connection()->getObject(p->objectId(),
[this](const ObjectPtr& functions, Message::ErrorCode ec)
[this](const ObjectPtr& functions, Message::ErrorCode /*ec*/)
{
m_functionsRequestId = Connection::invalidRequestId;
@ -113,7 +113,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
for(const QString& id : *items)
{
const int functionRequestId = functions->connection()->getObject(id,
[this, i](const ObjectPtr& function, Message::ErrorCode ec)
[this, i](const ObjectPtr& function, Message::ErrorCode /*ec*/)
{
m_functionRequestIds.erase(i);
@ -263,9 +263,7 @@ ThrottleFunctionButton* ThrottleWidget::getFunctionButton(DecoderFunctionFunctio
auto it = std::find_if(m_functionButtons.begin(), m_functionButtons.end(),
[function](const auto* btn)
{
auto n = btn->number();
auto f = btn->function();
return f == function;
return btn->function() == function;
});
return (it != m_functionButtons.end()) ? *it : nullptr;
}

Datei anzeigen

@ -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)

Datei anzeigen

@ -0,0 +1,3 @@
# Lua scripting {#lua}
TODO

Datei anzeigen

@ -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` | |

Datei anzeigen

@ -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 | |

Datei anzeigen

@ -0,0 +1,4 @@
# Enums {#lua-enums}
- [`world_event`](enum/worldevent.md)
- [`world_scale`](enum/worldscale.md)

Datei anzeigen

@ -0,0 +1,59 @@
# Globals {#lua-globals}
TODO
## Constants
`CODENAME` - Traintastic release name or development branch, e.g. `"master"`.
`LUA_VERSION` - Lua version and copyright, e.g. `"Lua 5.3.3 Copyright (C) 1994-2016 Lua.org, PUC-Rio"`
`VERSION` - Traintastic version, e.g. `"0.1.0"`
`VERSION_MAJOR` - Traintastic major version, e.g. `0`
`VERSION_MINOR` - Traintastic minor version, e.g. `1`
`VERSION_PATCH` - Traintastic patch level, e.g. `0`
## Functions
`assert()`
`get_class()`
`ipairs()`
`next()`
`pairs()`
`tonumber()`
`tostring()`
`type()`
## Objects
`log`
`world`
## ???
`class`
`enum`
`math`
`set`
`string`
`table`

Datei anzeigen

@ -0,0 +1,15 @@
# Board {#lua-object-board}
## Properties
`string id`
`string name`
## Methods
*None.*
## Events
*None.*

Datei anzeigen

@ -0,0 +1,25 @@
# Log {#lua-object-log}
## Properties
*None.*
## Methods
`debug(...)`
`info(...)`
`notice(...)`
`warning(...)`
`error(...)`
`critical(...)`
`fatal(...)`
## Events
*None.*

Datei anzeigen

@ -0,0 +1,29 @@
# World {#lua-object-world}
## Properties
`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()`
`stop()`
## Events
`on_event(world_state state, world_event event)`

Datei anzeigen

@ -0,0 +1,5 @@
# Objects {#lua-objects}
- [Board](object/board.md)
- [Log](object/log.md)
- [World](object/world.md)

Datei anzeigen

@ -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` | |

Datei anzeigen

@ -0,0 +1,3 @@
# Sets {#lua-sets}
- [`world_state`](set/worldstate.md)

Datei anzeigen

@ -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).

Datei anzeigen

@ -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).

Datei anzeigen

@ -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).

Datei anzeigen

@ -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).

Datei anzeigen

@ -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).

Datei anzeigen

@ -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).

Datei anzeigen

@ -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).

Datei anzeigen

@ -1,3 +0,0 @@
# Scripting {#scripting}
TODO

Datei anzeigen

@ -60,7 +60,56 @@
},
{
"type": "chapter",
"markdown": "scripting.md"
"markdown": "lua.md",
"code": "lua",
"pages": [
{
"markdown": "lua/globals.md",
"code": "lua"
},
{
"markdown": "lua/enums.md",
"code": "lua",
"pages": [
{
"markdown": "lua/enum/worldevent.md",
"code": "lua"
},
{
"markdown": "lua/enum/worldscale.md",
"code": "lua"
}
]
},
{
"markdown": "lua/sets.md",
"code": "lua",
"pages": [
{
"markdown": "lua/set/worldstate.md",
"code": "lua"
}
]
},
{
"markdown": "lua/objects.md",
"code": "lua",
"pages": [
{
"markdown": "lua/object/board.md",
"code": "lua"
},
{
"markdown": "lua/object/log.md",
"code": "lua"
},
{
"markdown": "lua/object/world.md",
"code": "lua"
}
]
}
]
},
{
"type": "chapter",

Datei anzeigen

@ -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'<h([1-6])([^>]*)>(.*) {#([a-z0-9-]+)}</h\1>', r'<h\1\2 id="\4">\3</h\1>', html)
@ -21,6 +21,9 @@ class HTMLBuilder(Builder):
# set target="_blank" for external links:
html = re.sub(r'<a([^>]+href="http(s|)://)', r'<a target="_blank"\1', html)
if 'code' in page and page['code'] == 'lua':
html = re.sub(r'<code>(.+)</code>', self._highlight_lua, html)
# change img title attribute to figcaption
html = re.sub(r'(<img[^>]+)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'<span class="const">\1</span>', 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'<span class="keyword">\1</span>', code) # keywords
return '<code class="lua">' + code + '</code>'

Datei anzeigen

@ -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'<h([1-5])([^>]*)>(.*?)</h\1>', lambda m: '<h' + str(min(6, int(m.group(1)) + depth)) + m.group(2) + '>' + m.group(3) + '</h' + str(min(6, int(m.group(1))) + depth) + '>', 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'<h([1-5])([^>]*)>(.*?)</h\1>', lambda m: '<h' + str(int(m.group(1)) + 1) + m.group(2) + '>' + m.group(3) + '</h' + str(int(m.group(1)) + 1) + '>', subhtml)
page_html += subhtml
page_html = self._file_to_html(page) + self.subpages(page)
m = re.findall(r'<h([1-2])([^>]*)>(.*?)</h\1>', page_html)
if m is not None:

Datei anzeigen

@ -31,7 +31,7 @@
Board::Board(const std::weak_ptr<World>& 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},
@ -46,23 +46,36 @@ Board::Board(const std::weak_ptr<World>& 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(it->second->tileId() == TileId::RailStraight && tileClassId == StraightRailTile::classId) // 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(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 = 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;

Datei anzeigen

@ -27,7 +27,7 @@
class BufferStopRailTile : public RailTile
{
CLASS_ID("board_tile.rail.bufferstop")
CLASS_ID("board_tile.rail.buffer_stop")
CREATE(BufferStopRailTile)
public:

Datei anzeigen

@ -52,3 +52,13 @@ std::shared_ptr<Tile> Tiles::create(const std::shared_ptr<World>& world, std::st
IF_CLASSID_CREATE(TunnelRailTile)
return std::shared_ptr<Tile>();
}
bool Tiles::canUpgradeStraightRail(std::string_view classId)
{
return
(classId == TunnelRailTile::classId) ||
(classId == BlockRailTile::classId) ||
(classId == SensorRailTile::classId) ||
(classId == Signal2AspectRailTile::classId) ||
(classId == Signal3AspectRailTile::classId);
}

Datei anzeigen

@ -82,6 +82,8 @@ struct Tiles
);
static std::shared_ptr<Tile> create(const std::shared_ptr<World>& world, std::string_view classId, std::string_view id = {});
static bool canUpgradeStraightRail(std::string_view classId);
};
#endif

Datei anzeigen

@ -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<AbstractEventHandler> handler)
{
assert(handler);
assert(&handler->event() == this);
m_handlers.emplace_back(std::move(handler));
}
bool AbstractEvent::disconnect(const std::shared_ptr<AbstractEventHandler>& 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);
}

Datei anzeigen

@ -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 <list>
#include "eventflags.hpp"
#include "argument.hpp"
class AbstractEventHandler;
class AbstractEvent : public InterfaceItem
{
private:
const EventFlags m_flags;
std::list<std::shared_ptr<AbstractEventHandler>> m_handlers;
bool m_firing;
protected:
void fire(const Arguments& args);
public:
using ArgumentInfo = std::pair<ValueType, std::string_view>;
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<const ArgumentInfo*, size_t> argumentInfo() const = 0;
void connect(std::shared_ptr<AbstractEventHandler> handler);
bool disconnect(const std::shared_ptr<AbstractEventHandler>& handler);
};
#endif

Datei anzeigen

@ -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());
}

Datei anzeigen

@ -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<AbstractEventHandler>
{
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

Datei anzeigen

@ -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}
{
}

Datei anzeigen

@ -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,13 +24,17 @@
#define TRAINTASTIC_SERVER_CORE_ABSTRACTMETHOD_HPP
#include "interfaceitem.hpp"
#include "methodflags.hpp"
#include <vector>
#include <variant>
#include <stdexcept>
#include "objectptr.hpp"
#include "argument.hpp"
class AbstractMethod : public InterfaceItem
{
private:
const MethodFlags m_flags;
public:
class MethodCallError : public std::runtime_error
{
@ -91,15 +95,18 @@ class AbstractMethod : public InterfaceItem
}
};
using Argument = std::variant<bool, int64_t, double, std::string, ObjectPtr>;
using Result = std::variant<std::monostate, bool, int64_t, double, std::string, ObjectPtr>;
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<ValueType> argumentTypes() const = 0;
virtual ValueType resultType() const = 0;
virtual Result call(const std::vector<Argument>& args) = 0;
virtual Result call(const Arguments& args) = 0;
};
#endif

Datei anzeigen

@ -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}
{
}

Datei anzeigen

@ -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<ObjectPtr>& items) = 0;
public:
Property<uint32_t> length;
AbstractObjectList(Object& _parent, const std::string& parentPropertyName);
virtual ObjectPtr getObject(uint32_t index) = 0;
};
#endif

35
server/src/core/argument.hpp Normale Datei
Datei anzeigen

@ -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 <cstdint>
#include <string>
#include <vector>
#include <variant>
#include "objectptr.hpp"
using Argument = std::variant<bool, int64_t, double, std::string, ObjectPtr>;
using Arguments = std::vector<Argument>;
#endif

Datei anzeigen

@ -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;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021 Reinder Feenstra
* Copyright (C) 2021-2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -34,6 +34,12 @@ TableModelPtr ControllerListBase::getModel()
return std::make_shared<ControllerListBaseTableModel>(*this);
}
ObjectPtr ControllerListBase::getObject(uint32_t index)
{
assert(index < m_items.size());
return std::static_pointer_cast<Object>(m_items[index]);
}
void ControllerListBase::add(ObjectPtr controller)
{
assert(controller);

Datei anzeigen

@ -54,6 +54,8 @@ class ControllerListBase : public AbstractObjectList
ControllerListBase(Object& _parent, const std::string& parentPropertyName);
TableModelPtr getModel() final;
ObjectPtr getObject(uint32_t index) final;
};
#endif

83
server/src/core/event.hpp Normale Datei
Datei anzeigen

@ -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... Args>
class Event : public AbstractEvent
{
friend class Object;
private:
template<class T, class... Tn>
inline void addArguments(Arguments& args, T value, Tn... others)
{
if constexpr(value_type_v<T> == ValueType::Enum || value_type_v<T> == ValueType::Set)
args.emplace_back(static_cast<int64_t>(value));
else
args.emplace_back(value);
if constexpr(sizeof...(Tn) > 0)
addArguments(args, others...);
}
template<class T>
static constexpr std::pair<ValueType, std::string_view> getArgumentInfo()
{
if constexpr(is_set_v<T>)
return {ValueType::Set, set_name_v<T>};
else if constexpr(std::is_enum_v<T>)
return {ValueType::Enum, EnumName<T>::value};
else
return {value_type_v<T>, {}};
}
static constexpr std::array<ArgumentInfo, sizeof...(Args)> s_argumentInfo = {{getArgumentInfo<Args>()...}};
protected:
void fire(Args... args)
{
Arguments arguments;
if constexpr(sizeof...(Args) > 0)
{
arguments.reserve(sizeof...(Args));
addArguments<Args...>(arguments, args...);
}
AbstractEvent::fire(arguments);
}
public:
Event(Object& object, const std::string& name, EventFlags flags) :
AbstractEvent(object, name, flags)
{
}
std::pair<const ArgumentInfo*, size_t> argumentInfo() const final
{
return {s_argumentInfo.data(), s_argumentInfo.size()};
}
};
#endif

Datei anzeigen

@ -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<EventFlags>(static_cast<std::underlying_type_t<EventFlags>>(lhs) & static_cast<std::underlying_type_t<EventFlags>>(rhs));
}
#endif

Datei anzeigen

@ -27,7 +27,7 @@
IdObject::IdObject(const std::weak_ptr<World>& 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);

Datei anzeigen

@ -44,7 +44,7 @@ template<std::size_t N, class... A>
using getArgumentType = typename std::tuple_element<N, std::tuple<A...>>::type;
template<std::size_t N, class... A>
auto getArgument(const AbstractMethod::Argument& value)
auto getArgument(const Argument& value)
{
using T = std::remove_const_t<std::remove_reference_t<getArgumentType<N, A...>>>;
@ -96,6 +96,12 @@ class Method<R(A...)> : public AbstractMethod
std::function<R(A...)> m_function;
public:
Method(Object& object, const std::string& name, MethodFlags flags, std::function<R(A...)> function) :
AbstractMethod(object, name, flags),
m_function{std::move(function)}
{
}
Method(Object& object, const std::string& name, std::function<R(A...)> function) :
AbstractMethod(object, name),
m_function{std::move(function)}

Datei anzeigen

@ -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<MethodFlags>(0);
#endif

Datei anzeigen

@ -35,6 +35,7 @@
static constexpr std::string_view classId = id; \
std::string_view getClassId() const override { return classId; }
template<class... Args> class Event;
class AbstractMethod;
class BaseProperty;
class AbstractProperty;
@ -55,6 +56,12 @@ class Object : public std::enable_shared_from_this<Object>
protected:
InterfaceItems m_interfaceItems;
template<class... Args>
inline void fireEvent(Event<Args...>& event, Args... args)
{
event.fire(std::forward<Args>(args)...);
}
inline bool dying() const noexcept { return m_dying; }
virtual void destroying() {}
virtual void load(WorldLoader& loader, const nlohmann::json& data);

Datei anzeigen

@ -76,18 +76,15 @@ class ObjectList : public AbstractObjectList
}
public:
Property<uint32_t> 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<Object>(m_items[index]);

Datei anzeigen

@ -260,7 +260,7 @@ bool Session::processMessage(const Message& message)
const ValueType resultType = message.read<ValueType>();
const uint8_t argumentCount = message.read<uint8_t>();
std::vector<AbstractMethod::Argument> args;
Arguments args;
for(uint8_t i = 0; i < argumentCount; i++)
{
switch(message.read<ValueType>())

Datei anzeigen

@ -0,0 +1,47 @@
/**
* 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);
}
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

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021 Reinder Feenstra
* Copyright (C) 2021-2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -23,6 +23,7 @@
#include "class.hpp"
#include "push.hpp"
#include "object.hpp"
#include "checkarguments.hpp"
#include "../board/board.hpp"
#include "../board/boardlist.hpp"
@ -96,78 +97,132 @@ static bool isInstance(const ::ObjectPtr& object)
}
template<class T>
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);
Lua::push(L, T::classId);
*reinterpret_cast<IsInstance*>(lua_newuserdata(L, sizeof(IsInstance))) = isInstance<T>;
luaL_newmetatable(L, metaTableName);
if(luaL_newmetatable(L, metaTableName))
{
lua_pushcfunction(L, Class::__tostring);
lua_setfield(L, -2, "__tostring");
}
lua_setmetatable(L, -2);
lua_rawset(L, -3);
lua_pop(L, 1);
// add to sandbox class global:
Lua::push(L, key);
Class::push<T>(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<Board>(L, "BOARD");
setField<BoardList>(L, "BOARD_LIST");
lua_newtable(L); // global
lua_setglobal(L, metaTableName);
setField<StraightRailTile>(L, "STRAIGHT_RAIL_TILE");
setField<TunnelRailTile>(L, "TUNNEL_RAIL_TILE");
setField<BufferStopRailTile>(L, "BUFFER_STOP_RAIL_TILE");
setField<Curve45RailTile>(L, "CURVE_45_RAIL_TILE");
setField<Curve90RailTile>(L, "CURVE_90_RAIL_TILE");
setField<Cross45RailTile>(L, "CROSS_45_RAIL_TILE");
setField<Cross90RailTile>(L, "CROSS_90_RAIL_TILE");
setField<Bridge45RightRailTile>(L, "BRIDGE_45_RIGHT_RAIL_TILE");
setField<Bridge45LeftRailTile>(L, "BRIDGE_45_LEFT_RAIL_TILE");
setField<Bridge90RailTile>(L, "BRIDGE_90_RAIL_TILE");
setField<TurnoutSingleSlipRailTile>(L, "TURNOUT_SINGLE_SLIP_RAIL_TILE");
setField<TurnoutLeft90RailTile>(L, "TURNOUT_LEFT_90_RAIL_TILE");
setField<TurnoutRight45RailTile>(L, "TURNOUT_RIGHT_45_RAIL_TILE");
setField<TurnoutLeft45RailTile>(L, "TURNOUT_LEFT_45_RAIL_TILE");
setField<TurnoutRight90RailTile>(L, "TURNOUT_RIGHT_90_RAIL_TILE");
setField<TurnoutDoubleSlipRailTile>(L, "TURNOUT_DOUBLE_SLIP_RAIL_TILE");
setField<TurnoutWyeRailTile>(L, "TURNOUT_WYE_RAIL_TILE");
setField<TurnoutLeftCurvedRailTile>(L, "TURNOUT__RAIL_TILE");
setField<Turnout3WayRailTile>(L, "TURNOUT_3WAY_RAIL_TILE");
setField<TurnoutRightCurvedRailTile>(L, "TURNOUT_RIGHT_CURVED_RAIL_TILE");
setField<Signal2AspectRailTile>(L, "SIGNAL_2_ASPECT_RAIL_TILE");
setField<Signal3AspectRailTile>(L, "SIGNAL_3_ASPECT_RAIL_TILE");
setField<SensorRailTile>(L, "SENSOR_RAIL_TILE");
setField<BlockRailTile>(L, "BLOCK_RAIL_TILE");
registerValue<Board>(L, "BOARD");
registerValue<BoardList>(L, "BOARD_LIST");
setField<Clock>(L, "CLOCK");
registerValue<StraightRailTile>(L, "STRAIGHT_RAIL_TILE");
registerValue<TunnelRailTile>(L, "TUNNEL_RAIL_TILE");
registerValue<BufferStopRailTile>(L, "BUFFER_STOP_RAIL_TILE");
registerValue<Curve45RailTile>(L, "CURVE_45_RAIL_TILE");
registerValue<Curve90RailTile>(L, "CURVE_90_RAIL_TILE");
registerValue<Cross45RailTile>(L, "CROSS_45_RAIL_TILE");
registerValue<Cross90RailTile>(L, "CROSS_90_RAIL_TILE");
registerValue<Bridge45RightRailTile>(L, "BRIDGE_45_RIGHT_RAIL_TILE");
registerValue<Bridge45LeftRailTile>(L, "BRIDGE_45_LEFT_RAIL_TILE");
registerValue<Bridge90RailTile>(L, "BRIDGE_90_RAIL_TILE");
registerValue<TurnoutSingleSlipRailTile>(L, "TURNOUT_SINGLE_SLIP_RAIL_TILE");
registerValue<TurnoutLeft90RailTile>(L, "TURNOUT_LEFT_90_RAIL_TILE");
registerValue<TurnoutRight45RailTile>(L, "TURNOUT_RIGHT_45_RAIL_TILE");
registerValue<TurnoutLeft45RailTile>(L, "TURNOUT_LEFT_45_RAIL_TILE");
registerValue<TurnoutRight90RailTile>(L, "TURNOUT_RIGHT_90_RAIL_TILE");
registerValue<TurnoutDoubleSlipRailTile>(L, "TURNOUT_DOUBLE_SLIP_RAIL_TILE");
registerValue<TurnoutWyeRailTile>(L, "TURNOUT_WYE_RAIL_TILE");
registerValue<TurnoutLeftCurvedRailTile>(L, "TURNOUT__RAIL_TILE");
registerValue<Turnout3WayRailTile>(L, "TURNOUT_3WAY_RAIL_TILE");
registerValue<TurnoutRightCurvedRailTile>(L, "TURNOUT_RIGHT_CURVED_RAIL_TILE");
registerValue<Signal2AspectRailTile>(L, "SIGNAL_2_ASPECT_RAIL_TILE");
registerValue<Signal3AspectRailTile>(L, "SIGNAL_3_ASPECT_RAIL_TILE");
registerValue<SensorRailTile>(L, "SENSOR_RAIL_TILE");
registerValue<BlockRailTile>(L, "BLOCK_RAIL_TILE");
setField<DecoderFunction>(L, "DECODER_FUNCTION");
setField<DecoderList>(L, "DECODER_LIST");
setField<Decoder>(L, "DECODER");
setField<DecoderFunctions>(L, "DECODER_FUNCTIONS");
registerValue<Clock>(L, "CLOCK");
setField<Input>(L, "INPUT");
setField<InputList>(L, "INPUT_LIST");
registerValue<DecoderFunction>(L, "DECODER_FUNCTION");
registerValue<DecoderList>(L, "DECODER_LIST");
registerValue<Decoder>(L, "DECODER");
registerValue<DecoderFunctions>(L, "DECODER_FUNCTIONS");
setField<Output>(L, "OUTPUT");
setField<OutputList>(L, "OUTPUT_LIST");
registerValue<Input>(L, "INPUT");
registerValue<InputList>(L, "INPUT_LIST");
setField<RailVehicleList>(L, "RAIL_VEHICLE_LIST");
setField<Locomotive>(L, "LOCOMOTIVE");
setField<FreightCar>(L, "FREIGHT_CAR");
registerValue<Output>(L, "OUTPUT");
registerValue<OutputList>(L, "OUTPUT_LIST");
setField<Train>(L, "TRAIN");
setField<TrainList>(L, "TRAIN_LIST");
registerValue<RailVehicleList>(L, "RAIL_VEHICLE_LIST");
registerValue<Locomotive>(L, "LOCOMOTIVE");
registerValue<FreightCar>(L, "FREIGHT_CAR");
setField<World>(L, "WORLD");
registerValue<Train>(L, "TRAIN");
registerValue<TrainList>(L, "TRAIN_LIST");
registerValue<World>(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::push(L, classId);
lua_rawget(L, -2);
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<IsInstance*>(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;
}

Datei anzeigen

@ -23,6 +23,7 @@
#ifndef TRAINTASTIC_SERVER_LUA_CLASS_HPP
#define TRAINTASTIC_SERVER_LUA_CLASS_HPP
#include <string_view>
#include <lua.hpp>
#include "../core/objectptr.hpp"
@ -32,7 +33,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<class T>
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);
};
}

Datei anzeigen

@ -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<typename T>
struct Enum
{
@ -53,11 +62,7 @@ struct Enum
static void push(lua_State* L, T value)
{
lua_getglobal(L, EnumName<T>::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<lua_Integer>(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<T>::value, static_cast<lua_Integer>(value));
}
static int __tostring(lua_State* L)

Datei anzeigen

@ -37,12 +37,16 @@ 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, "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(); }

134
server/src/lua/event.cpp Normale Datei
Datei anzeigen

@ -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<EventData**>(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<EventData**>(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<EventData**>(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<std::string_view>(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<EventData**>(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<EventHandler>(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;
}
}

53
server/src/lua/event.hpp Normale Datei
Datei anzeigen

@ -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 <lua.hpp>
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

Datei anzeigen

@ -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<bool>(arg));
break;
case ValueType::Enum:
pushEnum(m_L, argumentInfo.first[i].second.data(), std::get<int64_t>(arg));
break;
case ValueType::Integer:
push(m_L, std::get<int64_t>(arg));
break;
case ValueType::Float:
push(m_L, std::get<double>(arg));
break;
case ValueType::String:
push(m_L, std::get<std::string>(arg));
break;
case ValueType::Object:
push(m_L, std::get<ObjectPtr>(arg));
break;
case ValueType::Set:
pushSet(m_L, argumentInfo.first[i].second.data(), std::get<int64_t>(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<EventHandler>(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;
}
}
}

Datei anzeigen

@ -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 <lua.hpp>
#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

Datei anzeigen

@ -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");

Datei anzeigen

@ -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<AbstractMethod::Argument> args;
Arguments args;
args.reserve(argc);

Datei anzeigen

@ -25,33 +25,61 @@
#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 {
ObjectPtr Object::check(lua_State* L, int index)
{
ObjectPtrWeak& data = **static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableName));
if(ObjectPtr object = data.lock())
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
if(!data)
data = static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableName));
if(ObjectPtr object = (**data).lock())
return object;
else
errorDeadObject(L);
}
std::shared_ptr<AbstractObjectList> Object::checkList(lua_State* L, int index)
{
ObjectPtrWeak& data = **static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableNameList));
if(ObjectPtr object = data.lock())
return std::static_pointer_cast<AbstractObjectList>(object);
else
errorDeadObject(L);
}
ObjectPtr Object::test(lua_State* L, int index)
{
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableName));
if(!data)
return ObjectPtr();
data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
if(!data)
return {};
else if(ObjectPtr object = (**data).lock())
return object;
else
errorDeadObject(L);
}
std::shared_ptr<AbstractObjectList> Object::testList(lua_State* L, int index)
{
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
if(!data)
return {};
else if(ObjectPtr object = (**data).lock())
return std::static_pointer_cast<AbstractObjectList>(object);
else
errorDeadObject(L);
}
void Object::push(lua_State* L, const ObjectPtr& value)
{
if(value)
@ -62,7 +90,10 @@ void Object::push(lua_State* L, const ObjectPtr& value)
{
lua_pop(L, 1); // remove nil
*static_cast<ObjectPtrWeak**>(lua_newuserdata(L, sizeof(ObjectPtrWeak*))) = new ObjectPtrWeak(value);
luaL_setmetatable(L, metaTableName);
if(dynamic_cast<AbstractObjectList*>(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
}
@ -85,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
@ -104,47 +147,89 @@ 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<AbstractObjectList>(object))
{
if(index >= 1 && index <= list->length)
push(L, list->getObject(static_cast<uint32_t>(index - 1)));
else
lua_pushnil(L);
return 1;
}
}
}
std::string_view name{to<std::string_view>(L, 2)};
if(InterfaceItem* item = object->getItem(name))
{
// TODO: test scriptable
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(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:
case ValueType::Enum:
// EnumName<T>::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<lua_Integer>(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;
case ValueType::Set:
// set_name<T>::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<lua_Integer>(property->toInt64()));
break;
default:
assert(false);
lua_pushnil(L);
break;
}
}
else
lua_pushnil(L);
}
else if(AbstractMethod* method = dynamic_cast<AbstractMethod*>(item))
{
Method::push(L, *method);
if(method->isScriptCallable())
Method::push(L, *method);
else
lua_pushnil(L);
}
else if(auto* event = dynamic_cast<AbstractEvent*>(item))
{
if(event->isScriptable())
Event::push(L, *event);
else
lua_pushnil(L);
}
else
{
@ -165,9 +250,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
@ -209,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;
}
}

Datei anzeigen

@ -26,6 +26,7 @@
#include <lua.hpp>
#include <memory>
#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<AbstractObjectList> checkList(lua_State* L, int index);
static ObjectPtr test(lua_State* L, int index);
static std::shared_ptr<AbstractObjectList> testList(lua_State* L, int index);
static void push(lua_State* L, const ObjectPtr& value);

Datei anzeigen

@ -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,22 +24,104 @@
#include "push.hpp"
#include "object.hpp"
#include "method.hpp"
#include "event.hpp"
#include "eventhandler.hpp"
#include "log.hpp"
#include "class.hpp"
#include "to.hpp"
#include "type.hpp"
#include <version.hpp>
#include <traintastic/utils/str.hpp>
#include <traintastic/codename.hpp>
#include "../world/world.hpp"
#include "../enum/decoderprotocol.hpp"
#include "../enum/direction.hpp"
#include "../enum/worldevent.hpp"
#include "../enum/worldscale.hpp"
#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<std::string_view, 23> readOnlyGlobals = {{
// Lua baselib:
"assert",
"type",
"pairs",
"ipairs",
"next",
"tonumber",
"tostring",
"_G",
// Lua libs:
LUA_MATHLIBNAME,
LUA_STRLIBNAME,
LUA_TABLIBNAME,
// Constants:
"VERSION",
"VERSION_MAJOR",
"VERSION_MINOR",
"VERSION_PATCH",
"CODENAME",
"LUA_VERSION",
// Objects:
"world",
"log",
// Functions:
"is_instance",
// Type info:
"class",
"enum",
"set",
}};
static void addExtensions(lua_State* L, std::initializer_list<std::pair<const char*, lua_CFunction>> 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<const char*> names, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions = {})
{
// 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);
}
addExtensions(L, extensions);
}
static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunction, std::initializer_list<const char*> names, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions = {})
{
lua_createtable(L, 0, names.size() + extensions.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
addExtensions(L, extensions);
Lua::ReadOnlyTable::wrap(L, -1);
lua_setfield(L, -2, libraryName);
}
namespace Lua {
@ -49,15 +131,33 @@ 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<std::string_view>(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();
// load Lua baselib:
lua_pushcfunction(L, luaopen_base);
lua_pushliteral(L, "");
lua_call(L, 1, 0);
// create state data:
*static_cast<StateData**>(lua_getextraspace(L)) = new StateData(script);
@ -65,27 +165,67 @@ SandboxPtr Sandbox::create(Script& script)
Enum<DecoderProtocol>::registerType(L);
Enum<Direction>::registerType(L);
Enum<WorldEvent>::registerType(L);
Enum<WorldScale>::registerType(L);
Set<WorldState>::registerType(L);
Object::registerType(L);
Method::registerType(L);
Event::registerType(L);
// 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);
// 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")
// setup globals:
lua_newtable(L);
// 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",
"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, 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);
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");
@ -94,10 +234,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);
@ -109,6 +245,7 @@ SandboxPtr Sandbox::create(Script& script)
Enum<DecoderProtocol>::registerValues(L);
Enum<Direction>::registerValues(L);
Enum<WorldEvent>::registerValues(L);
Enum<WorldScale>::registerValues(L);
ReadOnlyTable::wrap(L, -1);
lua_setfield(L, -2, "enum");
@ -118,12 +255,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);
}
@ -135,7 +271,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
@ -167,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();
}
}
}

Datei anzeigen

@ -24,11 +24,15 @@
#define TRAINTASTIC_SERVER_LUA_SANDBOX_HPP
#include <memory>
#include <map>
#include <algorithm>
#include <cassert>
#include <lua.hpp>
namespace Lua {
class Script;
class EventHandler;
using SandboxPtr = std::unique_ptr<lua_State, void(*)(lua_State*)>;
@ -36,23 +40,66 @@ class Sandbox
{
private:
static void close(lua_State* L);
static int __index(lua_State* L);
static int __newindex(lua_State* L);
public:
class StateData
{
private:
Script& m_script;
lua_Integer m_eventHandlerId;
std::map<lua_Integer, std::shared_ptr<EventHandler>> 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<EventHandler> getEventHandler(lua_Integer id) const
{
auto it = m_eventHandlers.find(id);
if(it != m_eventHandlers.end())
return it->second;
else
return std::shared_ptr<EventHandler>();
}
inline lua_Integer registerEventHandler(std::shared_ptr<EventHandler> handler)
{
while(m_eventHandlers.find(m_eventHandlerId) != m_eventHandlers.end())
{
if(m_eventHandlerId == std::numeric_limits<lua_Integer>::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<EventHandler>& 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);

Datei anzeigen

@ -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()

Datei anzeigen

@ -49,6 +49,23 @@ struct set_values
template<typename T>
constexpr auto set_values_v = set_values<T>::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_Integer*>(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<typename T>
struct Set
{
@ -57,32 +74,20 @@ struct Set
static T check(lua_State* L, int index)
{
return *static_cast<T*>(luaL_checkudata(L, index, set_name_v<T>));
return static_cast<T>(*static_cast<lua_Integer*>(luaL_checkudata(L, index, set_name_v<T>)));
}
static bool test(lua_State* L, int index, T& value)
{
T* data = static_cast<T*>(luaL_testudata(L, index, set_name_v<T>));
lua_Integer* data = static_cast<lua_Integer*>(luaL_testudata(L, index, set_name_v<T>));
if(data)
value = *data;
value = static_cast<T>(*data);
return data;
}
static void push(lua_State* L, T value)
{
lua_getglobal(L, set_name_v<T>); // get tabel with all values: key=int, value=set as userdata
assert(lua_type(L, -1) == LUA_TTABLE);
lua_rawgeti(L, -1, static_cast<lua_Integer>(value)); // get userdata
if(lua_isnil(L, -1)) // value not in table
{
lua_pop(L, 1); // remove nil
*static_cast<T*>(lua_newuserdata(L, sizeof(value))) = value;
luaL_setmetatable(L, set_name_v<T>);
lua_pushvalue(L, -1); // copy set userdata on stack
lua_rawseti(L, -3, static_cast<lua_Integer>(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<T>, static_cast<lua_Integer>(value));
}
static int __add(lua_State* L)

80
server/src/lua/type.cpp Normale Datei
Datei anzeigen

@ -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;
}
}

34
server/src/lua/type.hpp Normale Datei
Datei anzeigen

@ -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 <lua.hpp>
namespace Lua {
int type(lua_State* L);
}
#endif

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen