Merge branch 'master' into interface-controller
Dieser Commit ist enthalten in:
Commit
11d4ebcd85
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -386,7 +386,7 @@ jobs:
|
|||||||
run: git lfs checkout
|
run: git lfs checkout
|
||||||
|
|
||||||
- name: Install python packages
|
- name: Install python packages
|
||||||
run: sudo pip3 install pycmarkgfm
|
run: sudo pip3 install cmarkgfm
|
||||||
|
|
||||||
- name: Build manual
|
- name: Build manual
|
||||||
working-directory: ${{github.workspace}}/manual
|
working-directory: ${{github.workspace}}/manual
|
||||||
|
|||||||
@ -34,7 +34,7 @@ The project goal is to develop open source software that can control everything
|
|||||||
- liblua5.3 (Linux only)
|
- liblua5.3 (Linux only)
|
||||||
- Manual:
|
- Manual:
|
||||||
- Python 3.6+ (older versions untested)
|
- 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.
|
Note: When cloning the source from git, git-lfs is required.
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,7 @@ constexpr QRect updateTileRect(const int x, const int y, const int w, const int
|
|||||||
|
|
||||||
BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
|
m_colorScheme{&BoardColorScheme::dark},
|
||||||
m_board{board},
|
m_board{board},
|
||||||
m_boardLeft{board.board().getProperty("left")},
|
m_boardLeft{board.board().getProperty("left")},
|
||||||
m_boardTop{board.board().getProperty("top")},
|
m_boardTop{board.board().getProperty("top")},
|
||||||
@ -84,11 +85,12 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
|||||||
if(Q_LIKELY(m_boardBottom))
|
if(Q_LIKELY(m_boardBottom))
|
||||||
connect(m_boardBottom, &AbstractProperty::valueChanged, this, &BoardAreaWidget::updateMinimumSize);
|
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())
|
for(const auto& [l, object] : m_board.board().tileObjects())
|
||||||
tileObjectAdded(l.x, l.y, object);
|
tileObjectAdded(l.x, l.y, object);
|
||||||
|
|
||||||
|
settingsChanged();
|
||||||
updateMinimumSize();
|
updateMinimumSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,12 +347,15 @@ void BoardAreaWidget::mouseMoveEvent(QMouseEvent* event)
|
|||||||
|
|
||||||
case MouseMoveAction::ResizeTile:
|
case MouseMoveAction::ResizeTile:
|
||||||
update(updateTileRect(
|
update(updateTileRect(
|
||||||
m_mouseMoveHideTileLocation.x,
|
m_mouseMoveHideTileLocation.x - originX,
|
||||||
m_mouseMoveHideTileLocation.y,
|
m_mouseMoveHideTileLocation.y - originY,
|
||||||
std::max(1, 1 + std::max(old.x, tl.x) - m_mouseMoveHideTileLocation.x),
|
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),
|
std::max(1, 1 + std::max(old.y, tl.y) - m_mouseMoveHideTileLocation.y),
|
||||||
tileSize));
|
tileSize));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MouseMoveAction::None:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,8 +379,9 @@ void BoardAreaWidget::wheelEvent(QWheelEvent* event)
|
|||||||
|
|
||||||
void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||||
{
|
{
|
||||||
|
assert(m_colorScheme);
|
||||||
|
|
||||||
const bool showBlockSensorStates = BoardSettings::instance().showBlockSensorStates;
|
const bool showBlockSensorStates = BoardSettings::instance().showBlockSensorStates;
|
||||||
const QColor backgroundColor = TilePainter::backgroundColor;
|
|
||||||
const QColor backgroundColor50{0x10, 0x10, 0x10, 0x80};
|
const QColor backgroundColor50{0x10, 0x10, 0x10, 0x80};
|
||||||
const QColor backgroundColorError50{0xff, 0x00, 0x00, 0x80};
|
const QColor backgroundColorError50{0xff, 0x00, 0x00, 0x80};
|
||||||
const QColor gridColor{0x40, 0x40, 0x40};
|
const QColor gridColor{0x40, 0x40, 0x40};
|
||||||
@ -388,7 +394,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
|||||||
|
|
||||||
const QRect viewport = rectToViewport(event->rect(), gridSize);
|
const QRect viewport = rectToViewport(event->rect(), gridSize);
|
||||||
|
|
||||||
painter.fillRect(viewport, backgroundColor);
|
painter.fillRect(viewport, m_colorScheme->background);
|
||||||
|
|
||||||
// draw grid:
|
// draw grid:
|
||||||
switch(m_grid)
|
switch(m_grid)
|
||||||
@ -415,7 +421,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
|||||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
// draw tiles:
|
// draw tiles:
|
||||||
TilePainter tilePainter{painter, tileSize};
|
TilePainter tilePainter{painter, tileSize, *m_colorScheme};
|
||||||
|
|
||||||
const int tileOriginX = boardLeft();
|
const int tileOriginX = boardLeft();
|
||||||
const int tileOriginY = boardTop();
|
const int tileOriginY = boardTop();
|
||||||
@ -531,9 +537,30 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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()
|
void BoardAreaWidget::updateMinimumSize()
|
||||||
{
|
{
|
||||||
const int tileSize = getTileSize() - 1;
|
const int tileSize = getTileSize() - 1;
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
#include <traintastic/enum/signalaspect.hpp>
|
#include <traintastic/enum/signalaspect.hpp>
|
||||||
#include <traintastic/enum/tristate.hpp>
|
#include <traintastic/enum/tristate.hpp>
|
||||||
#include <traintastic/enum/turnoutposition.hpp>
|
#include <traintastic/enum/turnoutposition.hpp>
|
||||||
|
#include "boardcolorscheme.hpp"
|
||||||
#include "../network/abstractproperty.hpp"
|
#include "../network/abstractproperty.hpp"
|
||||||
#include "../network/objectptr.hpp"
|
#include "../network/objectptr.hpp"
|
||||||
|
|
||||||
@ -57,6 +58,9 @@ class BoardAreaWidget : public QWidget
|
|||||||
ResizeTile,
|
ResizeTile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
const BoardColorScheme* m_colorScheme;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static constexpr int boardMargin = 1; // tile
|
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 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; }
|
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;
|
TurnoutPosition getTurnoutPosition(const TileLocation& l) const;
|
||||||
BlockState getBlockState(const TileLocation& l) const;
|
BlockState getBlockState(const TileLocation& l) const;
|
||||||
std::vector<SensorState> getBlockSensorStates(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;
|
void paintEvent(QPaintEvent* event) final;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
void settingsChanged();
|
||||||
void updateMinimumSize();
|
void updateMinimumSize();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr int zoomLevelMin = 0;
|
static constexpr int zoomLevelMin = -2;
|
||||||
static constexpr int zoomLevelMax = 15;
|
static constexpr int zoomLevelMax = 15;
|
||||||
|
|
||||||
BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr);
|
BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr);
|
||||||
@ -116,6 +122,7 @@ class BoardAreaWidget : public QWidget
|
|||||||
Grid grid() const { return m_grid; }
|
Grid grid() const { return m_grid; }
|
||||||
void nextGrid();
|
void nextGrid();
|
||||||
int zoomLevel() const { return m_zoomLevel; }
|
int zoomLevel() const { return m_zoomLevel; }
|
||||||
|
float zoomRatio() const { return static_cast<float>(getTileSize()) / getTileSize(0); }
|
||||||
|
|
||||||
void setMouseMoveAction(MouseMoveAction action);
|
void setMouseMoveAction(MouseMoveAction action);
|
||||||
void setMouseMoveTileId(TileId id);
|
void setMouseMoveTileId(TileId id);
|
||||||
|
|||||||
53
client/src/board/boardcolorscheme.cpp
Normale Datei
53
client/src/board/boardcolorscheme.cpp
Normale Datei
@ -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},
|
||||||
|
};
|
||||||
47
client/src/board/boardcolorscheme.hpp
Normale Datei
47
client/src/board/boardcolorscheme.hpp
Normale Datei
@ -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
|
||||||
@ -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_45"), TileId::RailCurve45, 0xFF},
|
||||||
TileInfo{QStringLiteral("board_tile.rail.curve_90"), TileId::RailCurve90, 0xFF},
|
TileInfo{QStringLiteral("board_tile.rail.curve_90"), TileId::RailCurve90, 0xFF},
|
||||||
TileInfo{QStringLiteral(""), TileId::None, 0},
|
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.cross_90"), TileId::RailCross90, 0x03},
|
||||||
TileInfo{QStringLiteral("board_tile.rail.bridge_45_left"), TileId::RailBridge45Left, 0x0F},
|
TileInfo{QStringLiteral("board_tile.rail.bridge_45_left"), TileId::RailBridge45Left, 0x0F},
|
||||||
TileInfo{QStringLiteral("board_tile.rail.bridge_45_right"), TileId::RailBridge45Right, 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_statusBar{new QStatusBar(this)},
|
||||||
m_statusBarMessage{new QLabel(this)},
|
m_statusBarMessage{new QLabel(this)},
|
||||||
m_statusBarCoords{new QLabel(this)},
|
m_statusBarCoords{new QLabel(this)},
|
||||||
|
m_statusBarZoom{new QLabel(this)},
|
||||||
m_editActions{new QActionGroup(this)},
|
m_editActions{new QActionGroup(this)},
|
||||||
m_editRotate{TileRotate::Deg0}
|
m_editRotate{TileRotate::Deg0}
|
||||||
, m_tileMoveStarted{false}
|
, m_tileMoveStarted{false}
|
||||||
@ -107,7 +108,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
|||||||
connect(name, &AbstractProperty::valueChangedString, this, &BoardWidget::setWindowTitle);
|
connect(name, &AbstractProperty::valueChangedString, this, &BoardWidget::setWindowTitle);
|
||||||
setWindowTitle(name->toString());
|
setWindowTitle(name->toString());
|
||||||
}
|
}
|
||||||
QMenu* m;
|
QMenu* menu;
|
||||||
|
|
||||||
QVBoxLayout* l = new QVBoxLayout();
|
QVBoxLayout* l = new QVBoxLayout();
|
||||||
l->setMargin(0);
|
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->setToolTip(Locale::tr("qtapp:grid"));
|
||||||
m_toolButtonGrid->setPopupMode(QToolButton::MenuButtonPopup);
|
m_toolButtonGrid->setPopupMode(QToolButton::MenuButtonPopup);
|
||||||
connect(m_toolButtonGrid, &QToolButton::pressed, m_toolButtonGrid, &QToolButton::showMenu);
|
connect(m_toolButtonGrid, &QToolButton::pressed, m_toolButtonGrid, &QToolButton::showMenu);
|
||||||
m = new QMenu(this);
|
menu = new QMenu(this);
|
||||||
m_actionGridNone = m->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"),
|
m_actionGridNone = menu->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"),
|
||||||
[this]()
|
[this]()
|
||||||
{
|
{
|
||||||
m_boardArea->setGrid(BoardAreaWidget::Grid::None);
|
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]()
|
[this]()
|
||||||
{
|
{
|
||||||
m_boardArea->setGrid(BoardAreaWidget::Grid::Dot);
|
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]()
|
[this]()
|
||||||
{
|
{
|
||||||
m_boardArea->setGrid(BoardAreaWidget::Grid::Line);
|
m_boardArea->setGrid(BoardAreaWidget::Grid::Line);
|
||||||
});
|
});
|
||||||
m_toolButtonGrid->setMenu(m);
|
m_toolButtonGrid->setMenu(menu);
|
||||||
toolbar->addWidget(m_toolButtonGrid);
|
toolbar->addWidget(m_toolButtonGrid);
|
||||||
|
|
||||||
// edit toolbar:
|
// edit toolbar:
|
||||||
@ -190,10 +191,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
|||||||
}));
|
}));
|
||||||
m_editActionDelete->setCheckable(true);
|
m_editActionDelete->setCheckable(true);
|
||||||
m_editActionDelete->setData(-1);
|
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));
|
m_editActionDelete->setEnabled(method->getAttributeBool(AttributeName::Enabled, true));
|
||||||
connect(m, &Method::attributeChanged, this,
|
connect(method, &Method::attributeChanged, this,
|
||||||
[this](AttributeName name, const QVariant& value)
|
[this](AttributeName name, const QVariant& value)
|
||||||
{
|
{
|
||||||
if(name == AttributeName::Enabled)
|
if(name == AttributeName::Enabled)
|
||||||
@ -234,10 +235,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
|||||||
m_addActions.append(action);
|
m_addActions.append(action);
|
||||||
if(auto* tb = dynamic_cast<QToolButton*>(m_toolbarEdit->widgetForAction(action)))
|
if(auto* tb = dynamic_cast<QToolButton*>(m_toolbarEdit->widgetForAction(action)))
|
||||||
tb->setPopupMode(QToolButton::MenuButtonPopup);
|
tb->setPopupMode(QToolButton::MenuButtonPopup);
|
||||||
QMenu* m = new QMenu(this);
|
QMenu* toolbuttonMenu = new QMenu(this);
|
||||||
for(auto subAction : actions)
|
for(auto subAction : actions)
|
||||||
{
|
{
|
||||||
m->addAction(subAction);
|
toolbuttonMenu->addAction(subAction);
|
||||||
connect(subAction, &QAction::triggered, this,
|
connect(subAction, &QAction::triggered, this,
|
||||||
[this, action, subAction]()
|
[this, action, subAction]()
|
||||||
{
|
{
|
||||||
@ -251,7 +252,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
|||||||
action->setIcon(actions[0]->icon());
|
action->setIcon(actions[0]->icon());
|
||||||
action->setText(actions[0]->text());
|
action->setText(actions[0]->text());
|
||||||
action->setData(actions[0]->data());
|
action->setData(actions[0]->data());
|
||||||
action->setMenu(m);
|
action->setMenu(toolbuttonMenu);
|
||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
connect(action, &QAction::triggered, this,
|
connect(action, &QAction::triggered, this,
|
||||||
[this, action]()
|
[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);
|
{
|
||||||
|
const bool v = method->getAttributeBool(AttributeName::Enabled, true);
|
||||||
for(QAction* act : m_addActions)
|
for(QAction* act : m_addActions)
|
||||||
act->setEnabled(v);
|
act->setEnabled(v);
|
||||||
|
}
|
||||||
|
|
||||||
connect(m, &Method::attributeChanged, this,
|
connect(method, &Method::attributeChanged, this,
|
||||||
[this](AttributeName name, const QVariant& value)
|
[this](AttributeName name, const QVariant& value)
|
||||||
{
|
{
|
||||||
if(name == AttributeName::Enabled)
|
if(name == AttributeName::Enabled)
|
||||||
@ -301,10 +304,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
|||||||
if(Q_LIKELY(m_object))
|
if(Q_LIKELY(m_object))
|
||||||
m_object->callMethod("resize_to_contents");
|
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));
|
m_editActionResizeToContents->setEnabled(method->getAttributeBool(AttributeName::Enabled, true));
|
||||||
connect(m, &Method::attributeChanged, this,
|
connect(method, &Method::attributeChanged, this,
|
||||||
[this](AttributeName name, const QVariant& value)
|
[this](AttributeName name, const QVariant& value)
|
||||||
{
|
{
|
||||||
if(name == AttributeName::Enabled)
|
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_statusBarMessage, 1);
|
||||||
m_statusBar->addWidget(m_statusBarCoords, 0);
|
m_statusBar->addWidget(m_statusBarCoords, 0);
|
||||||
|
m_statusBar->addWidget(m_statusBarZoom, 0);
|
||||||
l->addWidget(m_statusBar);
|
l->addWidget(m_statusBar);
|
||||||
|
|
||||||
AbstractProperty* edit = m_object->connection()->world()->getProperty("edit");
|
AbstractProperty* edit = m_object->connection()->world()->getProperty("edit");
|
||||||
@ -382,6 +386,7 @@ void BoardWidget::zoomLevelChanged(int value)
|
|||||||
{
|
{
|
||||||
m_actionZoomIn->setEnabled(value < BoardAreaWidget::zoomLevelMax);
|
m_actionZoomIn->setEnabled(value < BoardAreaWidget::zoomLevelMax);
|
||||||
m_actionZoomOut->setEnabled(value > BoardAreaWidget::zoomLevelMin);
|
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)
|
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_tileMoveY = y;
|
||||||
m_tileMoveStarted = true;
|
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->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::MoveTile);
|
||||||
m_boardArea->setMouseMoveTileId(data.id());
|
m_boardArea->setMouseMoveTileId(tileData.id());
|
||||||
m_boardArea->setMouseMoveTileRotate(data.rotate());
|
m_boardArea->setMouseMoveTileRotate(tileData.rotate());
|
||||||
m_boardArea->setMouseMoveTileSize(data.width(), data.height());
|
m_boardArea->setMouseMoveTileSize(tileData.width(), tileData.height());
|
||||||
m_boardArea->setMouseMoveHideTileLocation(l);
|
m_boardArea->setMouseMoveHideTileLocation(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // drop
|
else // drop
|
||||||
{
|
{
|
||||||
m_object->moveTile(m_tileMoveX, m_tileMoveY, x, y, false,
|
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;
|
m_tileMoveStarted = false;
|
||||||
@ -449,10 +454,10 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
|||||||
m_tileResizeY = l.y;
|
m_tileResizeY = l.y;
|
||||||
m_tileResizeStarted = true;
|
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->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::ResizeTile);
|
||||||
m_boardArea->setMouseMoveTileId(data.id());
|
m_boardArea->setMouseMoveTileId(tileData.id());
|
||||||
m_boardArea->setMouseMoveTileRotate(data.rotate());
|
m_boardArea->setMouseMoveTileRotate(tileData.rotate());
|
||||||
m_boardArea->setMouseMoveHideTileLocation(l);
|
m_boardArea->setMouseMoveHideTileLocation(l);
|
||||||
m_boardArea->setMouseMoveTileSizeMax(widthMax, heightMax);
|
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())
|
h >= 1 && h <= std::numeric_limits<uint8_t>::max())
|
||||||
{
|
{
|
||||||
m_object->resizeTile(m_tileResizeX, m_tileResizeY, w, h,
|
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)
|
else if(act == m_editActionDelete)
|
||||||
{
|
{
|
||||||
m_object->deleteTile(x, y,
|
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();
|
const Qt::KeyboardModifiers kbMod = QApplication::keyboardModifiers();
|
||||||
if(kbMod == Qt::NoModifier || kbMod == Qt::ControlModifier)
|
if(kbMod == Qt::NoModifier || kbMod == Qt::ControlModifier)
|
||||||
m_object->addTile(x, y, m_editRotate, classId, 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()
|
void BoardWidget::rightClicked()
|
||||||
{
|
{
|
||||||
if(QAction* act = m_editActions->checkedAction())
|
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()))
|
||||||
{
|
{
|
||||||
|
if(QApplication::keyboardModifiers() == Qt::NoModifier)
|
||||||
m_editRotate += TileRotate::Deg45;
|
m_editRotate += TileRotate::Deg45;
|
||||||
|
else if(QApplication::keyboardModifiers() == Qt::ShiftModifier)
|
||||||
|
m_editRotate -= TileRotate::Deg45;
|
||||||
validRotate(m_editRotate, tileInfo[index].rotates);
|
validRotate(m_editRotate, tileInfo[index].rotates);
|
||||||
m_boardArea->setMouseMoveTileRotate(m_editRotate);
|
m_boardArea->setMouseMoveTileRotate(m_editRotate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoardWidget::actionSelected(const TileInfo* tileInfo)
|
void BoardWidget::actionSelected(const TileInfo* info)
|
||||||
{
|
{
|
||||||
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None);
|
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None);
|
||||||
m_tileMoveStarted = false;
|
m_tileMoveStarted = false;
|
||||||
m_tileResizeStarted = 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->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::AddTile);
|
||||||
m_boardArea->setMouseMoveTileRotate(m_editRotate);
|
m_boardArea->setMouseMoveTileRotate(m_editRotate);
|
||||||
m_boardArea->setMouseMoveTileId(tileInfo->id);
|
m_boardArea->setMouseMoveTileId(info->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,6 +46,7 @@ class BoardWidget : public QWidget
|
|||||||
QStatusBar* m_statusBar;
|
QStatusBar* m_statusBar;
|
||||||
QLabel* m_statusBarMessage;
|
QLabel* m_statusBarMessage;
|
||||||
QLabel* m_statusBarCoords;
|
QLabel* m_statusBarCoords;
|
||||||
|
QLabel* m_statusBarZoom;
|
||||||
QAction* m_actionZoomIn;
|
QAction* m_actionZoomIn;
|
||||||
QAction* m_actionZoomOut;
|
QAction* m_actionZoomOut;
|
||||||
QToolButton* m_toolButtonGrid;
|
QToolButton* m_toolButtonGrid;
|
||||||
|
|||||||
@ -24,14 +24,20 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
|
#include "boardcolorscheme.hpp"
|
||||||
|
#include "../settings/boardsettings.hpp"
|
||||||
#include "../utils/rectf.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_trackWidth{tileSize / 5},
|
||||||
m_turnoutMargin{tileSize / 10},
|
m_turnoutMargin{tileSize / 10},
|
||||||
m_trackPen(trackColor, m_trackWidth, Qt::SolidLine, Qt::FlatCap),
|
m_blockPen{m_colorScheme.track},
|
||||||
m_trackErasePen(backgroundColor, m_trackWidth * 2, Qt::SolidLine, Qt::FlatCap),
|
m_trackPen(m_colorScheme.track, m_trackWidth, Qt::SolidLine, Qt::FlatCap),
|
||||||
m_turnoutStatePen(Qt::blue, (m_trackWidth + 1) / 2, 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}
|
m_painter{painter}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -136,9 +142,9 @@ void TilePainter::draw(TileId id, const QRectF& r, TileRotate rotate)
|
|||||||
const qreal m = r.width() / 5;
|
const qreal m = r.width() / 5;
|
||||||
const QRectF rArc = r.adjusted(m, m, -m, -m);
|
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.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);
|
m_painter.drawArc(rArc, angle, angleLength);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -157,7 +163,7 @@ void TilePainter::drawSensor(TileId id, const QRectF& r, TileRotate rotate, Sens
|
|||||||
setTrackPen();
|
setTrackPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
const qreal sz = r.width() / 4;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -170,7 +176,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
switch(id)
|
switch(id)
|
||||||
{
|
{
|
||||||
case TileId::RailTurnoutLeft45:
|
case TileId::RailTurnoutLeft45:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawCurve45(r, rotate);
|
drawCurve45(r, rotate);
|
||||||
|
|
||||||
@ -191,7 +197,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutLeft90:
|
case TileId::RailTurnoutLeft90:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawCurve90(r, rotate);
|
drawCurve90(r, rotate);
|
||||||
|
|
||||||
@ -212,7 +218,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutLeftCurved:
|
case TileId::RailTurnoutLeftCurved:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawCurve45(r, rotate);
|
drawCurve45(r, rotate);
|
||||||
drawCurve90(r, rotate);
|
drawCurve90(r, rotate);
|
||||||
|
|
||||||
@ -233,7 +239,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutRight45:
|
case TileId::RailTurnoutRight45:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||||
|
|
||||||
@ -254,7 +260,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutRight90:
|
case TileId::RailTurnoutRight90:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawCurve90(r, rotate + TileRotate::Deg270);
|
drawCurve90(r, rotate + TileRotate::Deg270);
|
||||||
|
|
||||||
@ -275,7 +281,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutRightCurved:
|
case TileId::RailTurnoutRightCurved:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||||
drawCurve90(r, rotate + TileRotate::Deg270);
|
drawCurve90(r, rotate + TileRotate::Deg270);
|
||||||
|
|
||||||
@ -296,7 +302,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutWye:
|
case TileId::RailTurnoutWye:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawCurve45(r, rotate);
|
drawCurve45(r, rotate);
|
||||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||||
|
|
||||||
@ -317,7 +323,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnout3Way:
|
case TileId::RailTurnout3Way:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawCurve45(r, rotate);
|
drawCurve45(r, rotate);
|
||||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||||
@ -343,7 +349,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutSingleSlip:
|
case TileId::RailTurnoutSingleSlip:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawStraight(r, rotate - TileRotate::Deg45);
|
drawStraight(r, rotate - TileRotate::Deg45);
|
||||||
drawCurve45(r, rotate);
|
drawCurve45(r, rotate);
|
||||||
@ -366,7 +372,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TileId::RailTurnoutDoubleSlip:
|
case TileId::RailTurnoutDoubleSlip:
|
||||||
setTrackPen();
|
setTurnoutPen();
|
||||||
drawStraight(r, rotate);
|
drawStraight(r, rotate);
|
||||||
drawStraight(r, rotate - TileRotate::Deg45);
|
drawStraight(r, rotate - TileRotate::Deg45);
|
||||||
drawCurve45(r, rotate);
|
drawCurve45(r, rotate);
|
||||||
@ -439,19 +445,19 @@ QColor TilePainter::sensorStateToColor(SensorState value) const
|
|||||||
switch(value)
|
switch(value)
|
||||||
{
|
{
|
||||||
case SensorState::Occupied:
|
case SensorState::Occupied:
|
||||||
return sensorColorOccupied;
|
return m_colorScheme.sensorOccupied;
|
||||||
|
|
||||||
case SensorState::Free:
|
case SensorState::Free:
|
||||||
return sensorColorFree;
|
return m_colorScheme.sensorFree;
|
||||||
|
|
||||||
case SensorState::Idle:
|
case SensorState::Idle:
|
||||||
return sensorColorIdle;
|
return m_colorScheme.sensorIdle;
|
||||||
|
|
||||||
case SensorState::Triggered:
|
case SensorState::Triggered:
|
||||||
return sensorColorTriggered;
|
return m_colorScheme.sensorTriggered;
|
||||||
|
|
||||||
case SensorState::Unknown:
|
case SensorState::Unknown:
|
||||||
return sensorColorUnknown;
|
return m_colorScheme.sensorUnknown;
|
||||||
}
|
}
|
||||||
assert(false);
|
assert(false);
|
||||||
return QColor();
|
return QColor();
|
||||||
@ -462,15 +468,15 @@ void TilePainter::setBlockStateBrush(BlockState value)
|
|||||||
switch(value)
|
switch(value)
|
||||||
{
|
{
|
||||||
case BlockState::Occupied:
|
case BlockState::Occupied:
|
||||||
m_painter.setBrush(blockBrushOccupied);
|
m_painter.setBrush(m_colorScheme.blockOccupied);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BlockState::Free:
|
case BlockState::Free:
|
||||||
m_painter.setBrush(blockBrushFree);
|
m_painter.setBrush(m_colorScheme.blockFree);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BlockState::Unknown:
|
case BlockState::Unknown:
|
||||||
m_painter.setBrush(blockBrushUnknown);
|
m_painter.setBrush(m_colorScheme.blockUnknown);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -768,7 +774,7 @@ void TilePainter::drawRailBlock(const QRectF& r, TileRotate rotate, BlockState s
|
|||||||
{
|
{
|
||||||
m_painter.drawLine(topCenter(r), bottomCenter(r));
|
m_painter.drawLine(topCenter(r), bottomCenter(r));
|
||||||
setBlockStateBrush(state);
|
setBlockStateBrush(state);
|
||||||
m_painter.setPen(blockPen);
|
m_painter.setPen(m_blockPen);
|
||||||
const qreal m = 0.5 + qFloor(r.width() / 10);
|
const qreal m = 0.5 + qFloor(r.width() / 10);
|
||||||
const QRectF block = r.adjusted(m, m, -m, -m);
|
const QRectF block = r.adjusted(m, m, -m, -m);
|
||||||
m_painter.drawRect(block);
|
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));
|
m_painter.drawLine(centerLeft(r), centerRight(r));
|
||||||
setBlockStateBrush(state);
|
setBlockStateBrush(state);
|
||||||
m_painter.setPen(blockPen);
|
m_painter.setPen(m_blockPen);
|
||||||
const qreal m = 0.5 + qFloor(r.height() / 10);
|
const qreal m = 0.5 + qFloor(r.height() / 10);
|
||||||
const QRectF block = r.adjusted(m, m, -m, -m);
|
const QRectF block = r.adjusted(m, m, -m, -m);
|
||||||
m_painter.drawRect(block);
|
m_painter.drawRect(block);
|
||||||
|
|||||||
@ -34,38 +34,34 @@
|
|||||||
#include <traintastic/enum/tristate.hpp>
|
#include <traintastic/enum/tristate.hpp>
|
||||||
#include <traintastic/enum/turnoutposition.hpp>
|
#include <traintastic/enum/turnoutposition.hpp>
|
||||||
|
|
||||||
|
struct BoardColorScheme;
|
||||||
|
|
||||||
class TilePainter
|
class TilePainter
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
inline static const QColor backgroundColor{0x10, 0x10, 0x10};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline static const QColor trackColor{0xC0, 0xC0, 0xC0};
|
|
||||||
inline static const QColor signalRed{192, 0, 0};
|
inline static const QColor signalRed{192, 0, 0};
|
||||||
inline static const QColor signalYellow{192, 192, 32};
|
inline static const QColor signalYellow{192, 192, 32};
|
||||||
inline static const QColor signalGreen{0, 192, 0};
|
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_trackWidth;
|
||||||
const int m_turnoutMargin;
|
const int m_turnoutMargin;
|
||||||
|
const QPen m_blockPen;
|
||||||
const QPen m_trackPen;
|
const QPen m_trackPen;
|
||||||
|
const QPen m_trackDisabledPen;
|
||||||
const QPen m_trackErasePen;
|
const QPen m_trackErasePen;
|
||||||
|
const QPen m_turnoutPen;
|
||||||
const QPen m_turnoutStatePen;
|
const QPen m_turnoutStatePen;
|
||||||
|
|
||||||
QPainter& m_painter;
|
QPainter& m_painter;
|
||||||
|
|
||||||
inline void setTrackPen() { m_painter.setPen(m_trackPen); }
|
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 setTrackErasePen() { m_painter.setPen(m_trackErasePen); }
|
||||||
inline void setTurnoutStatePen() { m_painter.setPen(m_turnoutStatePen); }
|
inline void setTurnoutPen() { m_painter.setPen(m_turnoutDrawState ? m_trackPen : m_trackDisabledPen); }
|
||||||
inline QRectF turnoutStateRect(const QRectF& r) { return r.adjusted(m_turnoutMargin, m_turnoutMargin, -m_turnoutMargin, -m_turnoutMargin); }
|
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;
|
QColor sensorStateToColor(SensorState value) const;
|
||||||
void setBlockStateBrush(BlockState value);
|
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 = {});
|
void drawRailBlock(const QRectF& r, TileRotate rotate, BlockState state = BlockState::Unknown, const std::vector<SensorState> subStates = {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TilePainter(QPainter& painter, int tileSize);
|
TilePainter(QPainter& painter, int tileSize, const BoardColorScheme& colorScheme);
|
||||||
|
|
||||||
void draw(TileId id, const QRectF& r, TileRotate rotate);
|
void draw(TileId id, const QRectF& r, TileRotate rotate);
|
||||||
void drawSensor(TileId id, const QRectF& r, TileRotate rotate, SensorState state = SensorState::Unknown);
|
void drawSensor(TileId id, const QRectF& r, TileRotate rotate, SensorState state = SensorState::Unknown);
|
||||||
|
|||||||
@ -82,7 +82,7 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par
|
|||||||
m_object = object;
|
m_object = object;
|
||||||
|
|
||||||
m_requestId = m_item.object().connection()->getTableModel(m_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)
|
if(tableModel)
|
||||||
{
|
{
|
||||||
@ -104,7 +104,7 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par
|
|||||||
delete spinner;
|
delete spinner;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
|
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -72,7 +72,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
|
|||||||
m_object = object;
|
m_object = object;
|
||||||
|
|
||||||
m_requestId = m_connection->getTableModel(m_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)
|
if(tableModel)
|
||||||
{
|
{
|
||||||
@ -95,7 +95,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
|
|||||||
delete spinner;
|
delete spinner;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
|
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -46,7 +46,7 @@ void MdiArea::addBackgroundAction(QAction* action)
|
|||||||
|
|
||||||
void MdiArea::removeBackgroundAction(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())
|
if(it != m_backgroundActionButtons.end())
|
||||||
{
|
{
|
||||||
delete it->second;
|
delete it->second;
|
||||||
|
|||||||
@ -72,10 +72,10 @@ class AbstractProperty : public BaseProperty
|
|||||||
return setValueInt64(static_cast<int64_t>(value));
|
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 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 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 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 setValueString(const QString& value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valueChanged();
|
void valueChanged();
|
||||||
|
|||||||
@ -39,12 +39,12 @@ class AbstractVectorProperty : public BaseProperty
|
|||||||
public:
|
public:
|
||||||
virtual int size() const = 0;
|
virtual int size() const = 0;
|
||||||
|
|
||||||
virtual bool getBool(int index) const { Q_ASSERT(false); return false; }
|
virtual bool getBool(int /*index*/) const { Q_ASSERT(false); return false; }
|
||||||
virtual int getInt(int index) const { Q_ASSERT(false); return 0; }
|
virtual int getInt(int /*index*/) const { Q_ASSERT(false); return 0; }
|
||||||
virtual int64_t getInt64(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 double getDouble(int /*index*/) const { Q_ASSERT(false); return 0; }
|
||||||
virtual QString getString(int index) const { Q_ASSERT(false); return ""; }
|
virtual QString getString(int /*index*/) const { Q_ASSERT(false); return ""; }
|
||||||
virtual QVariant getVariant(int index) const { Q_ASSERT(false); return QVariant(); }
|
virtual QVariant getVariant(int /*index*/) const { Q_ASSERT(false); return QVariant(); }
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T getEnum(int index) const
|
T getEnum(int index) const
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
#include "connection.hpp"
|
#include "connection.hpp"
|
||||||
#include "callmethod.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),
|
Object(std::move(connection), handle, classId),
|
||||||
m_getTileDataRequestId{Connection::invalidRequestId}
|
m_getTileDataRequestId{Connection::invalidRequestId}
|
||||||
{
|
{
|
||||||
|
|||||||
@ -56,7 +56,7 @@ class Board final : public Object
|
|||||||
public:
|
public:
|
||||||
inline static const QString classId = QStringLiteral("board");
|
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;
|
~Board() final;
|
||||||
|
|
||||||
void getTileData();
|
void getTileData();
|
||||||
|
|||||||
@ -483,7 +483,7 @@ ObjectPtr Connection::readObject(const Message& message)
|
|||||||
else if(classId.startsWith(OutputMap::classIdPrefix))
|
else if(classId.startsWith(OutputMap::classIdPrefix))
|
||||||
p = new OutputMap(shared_from_this(), handle, classId);
|
p = new OutputMap(shared_from_this(), handle, classId);
|
||||||
else if(classId == Board::classId)
|
else if(classId == Board::classId)
|
||||||
p = new Board(shared_from_this(), handle, classId);
|
p = new Board(shared_from_this(), handle);
|
||||||
else
|
else
|
||||||
p = new Object(shared_from_this(), handle, classId);
|
p = new Object(shared_from_this(), handle, classId);
|
||||||
|
|
||||||
@ -593,14 +593,14 @@ ObjectPtr Connection::readObject(const Message& message)
|
|||||||
{
|
{
|
||||||
message.readBlock(); // item
|
message.readBlock(); // item
|
||||||
const AttributeName attributeName = message.read<AttributeName>();
|
const AttributeName attributeName = message.read<AttributeName>();
|
||||||
const ValueType type = message.read<ValueType>();
|
const ValueType valueType = message.read<ValueType>();
|
||||||
|
|
||||||
switch(message.read<AttributeType>())
|
switch(message.read<AttributeType>())
|
||||||
{
|
{
|
||||||
case AttributeType::Value:
|
case AttributeType::Value:
|
||||||
{
|
{
|
||||||
QVariant value;
|
QVariant value;
|
||||||
switch(type)
|
switch(valueType)
|
||||||
{
|
{
|
||||||
case ValueType::Boolean:
|
case ValueType::Boolean:
|
||||||
value = message.read<bool>();
|
value = message.read<bool>();
|
||||||
@ -632,7 +632,7 @@ ObjectPtr Connection::readObject(const Message& message)
|
|||||||
case AttributeType::Values:
|
case AttributeType::Values:
|
||||||
{
|
{
|
||||||
const int length = message.read<int>(); // read uint32_t as int, Qt uses int for length
|
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))
|
if(Q_LIKELY(values.length() == length))
|
||||||
item->m_attributes[attributeName] = values;
|
item->m_attributes[attributeName] = values;
|
||||||
break;
|
break;
|
||||||
@ -683,7 +683,7 @@ void Connection::getWorld()
|
|||||||
setWorld(nullptr);
|
setWorld(nullptr);
|
||||||
else
|
else
|
||||||
m_worldRequestId = getObject(m_worldProperty->objectId(),
|
m_worldRequestId = getObject(m_worldProperty->objectId(),
|
||||||
[this](const ObjectPtr& object, Message::ErrorCode ec)
|
[this](const ObjectPtr& object, Message::ErrorCode /*ec*/)
|
||||||
{
|
{
|
||||||
m_worldRequestId = invalidRequestId;
|
m_worldRequestId = invalidRequestId;
|
||||||
setWorld(object);
|
setWorld(object);
|
||||||
@ -1039,22 +1039,22 @@ void Connection::processMessage(const std::shared_ptr<Message> message)
|
|||||||
|
|
||||||
void Connection::socketConnected()
|
void Connection::socketConnected()
|
||||||
{
|
{
|
||||||
std::unique_ptr<Message> request{Message::newRequest(Message::Command::Login)};
|
std::unique_ptr<Message> loginRequest{Message::newRequest(Message::Command::Login)};
|
||||||
request->write(m_username.toUtf8());
|
loginRequest->write(m_username.toUtf8());
|
||||||
request->write(m_password);
|
loginRequest->write(m_password);
|
||||||
send(request,
|
send(loginRequest,
|
||||||
[this](const std::shared_ptr<Message> message)
|
[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)};
|
std::unique_ptr<Message> newSessionRequest{Message::newRequest(Message::Command::NewSession)};
|
||||||
send(request,
|
send(newSessionRequest,
|
||||||
[this](const std::shared_ptr<Message> message)
|
[this](const std::shared_ptr<Message> newSessionResonse)
|
||||||
{
|
{
|
||||||
if(message && message->isResponse() && !message->isError())
|
if(newSessionResonse && newSessionResonse->isResponse() && !newSessionResonse->isError())
|
||||||
{
|
{
|
||||||
message->read(m_sessionUUID);
|
newSessionResonse->read(m_sessionUUID);
|
||||||
m_traintastic = readObject(*message);
|
m_traintastic = readObject(*newSessionResonse);
|
||||||
m_worldProperty = dynamic_cast<ObjectProperty*>(m_traintastic->getProperty("world"));
|
m_worldProperty = dynamic_cast<ObjectProperty*>(m_traintastic->getProperty("world"));
|
||||||
connect(m_worldProperty, &ObjectProperty::valueChanged, this,
|
connect(m_worldProperty, &ObjectProperty::valueChanged, this,
|
||||||
[this]()
|
[this]()
|
||||||
|
|||||||
@ -51,11 +51,11 @@ void OutputMap::getItems()
|
|||||||
{
|
{
|
||||||
m_getItemsRequestId = Connection::invalidRequestId;
|
m_getItemsRequestId = Connection::invalidRequestId;
|
||||||
|
|
||||||
if(auto c = connection())
|
if(auto con = connection())
|
||||||
{
|
{
|
||||||
Items objects;
|
Items objects;
|
||||||
while(!response->endOfMessage())
|
while(!response->endOfMessage())
|
||||||
objects.emplace_back(c->readObject(*response));
|
objects.emplace_back(con->readObject(*response));
|
||||||
m_items = std::move(objects);
|
m_items = std::move(objects);
|
||||||
|
|
||||||
emit itemsChanged();
|
emit itemsChanged();
|
||||||
|
|||||||
@ -122,7 +122,7 @@ void ServerLogTableModel::processMessage(const Message& message)
|
|||||||
log.code = message.read<LogMessage>();
|
log.code = message.read<LogMessage>();
|
||||||
log.message = Locale::tr("message:" + toCodeString(log.code));
|
log.message = Locale::tr("message:" + toCodeString(log.code));
|
||||||
const int argc = message.read<uint8_t>();
|
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>()));
|
log.message = log.message.arg(QString::fromUtf8(message.read<QByteArray>()));
|
||||||
m_logs.append(log);
|
m_logs.append(log);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,10 +31,43 @@ QString errorCodeToText(Message::ErrorCode ec)
|
|||||||
QString text;
|
QString text;
|
||||||
switch(ec)
|
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:
|
case ErrorCode::UnknownObject:
|
||||||
text = "Unknown object";
|
text = "Unknown object";
|
||||||
break;
|
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:
|
case ErrorCode::None:
|
||||||
break; // silence warning
|
break; // silence warning
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,14 +23,25 @@
|
|||||||
#ifndef TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
|
#ifndef TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
|
||||||
#define TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
|
#define TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include "settingsbase.hpp"
|
#include "settingsbase.hpp"
|
||||||
#include "setting.hpp"
|
#include "setting.hpp"
|
||||||
|
#include <traintastic/enum/enum.hpp>
|
||||||
|
|
||||||
class BoardSettings : public SettingsBase
|
class BoardSettings : public SettingsBase
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
enum class ColorScheme
|
||||||
|
{
|
||||||
|
Dark = 0,
|
||||||
|
Light = 1,
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BoardSettings()
|
BoardSettings()
|
||||||
: SettingsBase("board")
|
: SettingsBase("board")
|
||||||
|
, colorScheme{*this, "color_scheme", ColorScheme::Dark}
|
||||||
|
, turnoutDrawState{*this, "turnout_draw_state", true}
|
||||||
, showBlockSensorStates{*this, "show_block_sensor_states", true}
|
, showBlockSensorStates{*this, "show_block_sensor_states", true}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -42,7 +53,23 @@ class BoardSettings : public SettingsBase
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Setting<ColorScheme> colorScheme;
|
||||||
|
Setting<bool> turnoutDrawState;
|
||||||
Setting<bool> showBlockSensorStates;
|
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
|
#endif
|
||||||
|
|||||||
@ -28,6 +28,8 @@ BoardSettingsWidget::BoardSettingsWidget(QWidget* parent)
|
|||||||
{
|
{
|
||||||
BoardSettings& s = BoardSettings::instance();
|
BoardSettings& s = BoardSettings::instance();
|
||||||
|
|
||||||
|
addSetting(s.colorScheme);
|
||||||
|
addSetting(s.turnoutDrawState);
|
||||||
addSetting(s.showBlockSensorStates);
|
addSetting(s.showBlockSensorStates);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|||||||
@ -66,9 +66,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(QWidget* parent)
|
|||||||
{
|
{
|
||||||
GeneralSettings::instance().language.setValue(cb->itemData(index).toString());
|
GeneralSettings::instance().language.setValue(cb->itemData(index).toString());
|
||||||
});
|
});
|
||||||
add(GeneralSettings::instance().language.name(), cb);
|
add(s.language.name(), cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,12 @@
|
|||||||
|
|
||||||
#include "settingsbase.hpp"
|
#include "settingsbase.hpp"
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct SettingEnum
|
||||||
|
{
|
||||||
|
static_assert(sizeof(T) != sizeof(T));
|
||||||
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
class Setting
|
class Setting
|
||||||
{
|
{
|
||||||
|
|||||||
@ -39,11 +39,24 @@ class SettingsBase : public QObject
|
|||||||
template<class T>
|
template<class T>
|
||||||
inline T get(const QString& key, const T& defaultValue) const
|
inline T get(const QString& key, const T& defaultValue) const
|
||||||
{
|
{
|
||||||
|
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));
|
return qvariant_cast<T>(m_settings.value(key, defaultValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
inline void set(const QString& key, const T& value)
|
inline void set(const QString& key, const T& value)
|
||||||
|
{
|
||||||
|
if constexpr(std::is_enum_v<T>)
|
||||||
|
{
|
||||||
|
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)
|
if(m_settings.value(key) != value)
|
||||||
{
|
{
|
||||||
@ -51,6 +64,7 @@ class SettingsBase : public QObject
|
|||||||
emit changed();
|
emit changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SettingsBase(const QString& group)
|
SettingsBase(const QString& group)
|
||||||
|
|||||||
@ -24,7 +24,10 @@
|
|||||||
#define TRAINTASTIC_CLIENT_SETTINGS_SETTINGSBASEWIDGET_HPP
|
#define TRAINTASTIC_CLIENT_SETTINGS_SETTINGSBASEWIDGET_HPP
|
||||||
|
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <traintastic/enum/enum.hpp>
|
||||||
#include "setting.hpp"
|
#include "setting.hpp"
|
||||||
|
#include "../utils/enum.hpp"
|
||||||
|
|
||||||
class SettingsBaseWidget : public QScrollArea
|
class SettingsBaseWidget : public QScrollArea
|
||||||
{
|
{
|
||||||
@ -40,11 +43,31 @@ class SettingsBaseWidget : public QScrollArea
|
|||||||
void addSettingOnOff(Setting<bool>& setting);
|
void addSettingOnOff(Setting<bool>& setting);
|
||||||
void addSettingDir(Setting<QString>& 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>
|
template<class T>
|
||||||
inline void addSetting(Setting<T>& setting)
|
inline void addSetting(Setting<T>& setting)
|
||||||
{
|
{
|
||||||
if constexpr(std::is_same_v<T, bool>)
|
if constexpr(std::is_same_v<T, bool>)
|
||||||
addSettingOnOff(setting);
|
addSettingOnOff(setting);
|
||||||
|
else if constexpr(std::is_enum_v<T>)
|
||||||
|
addSettingEnumDropdown(setting);
|
||||||
else
|
else
|
||||||
static_assert(sizeof(T) != sizeof(T));
|
static_assert(sizeof(T) != sizeof(T));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -42,6 +42,7 @@
|
|||||||
#include <traintastic/enum/xpressnetcommandstation.hpp>
|
#include <traintastic/enum/xpressnetcommandstation.hpp>
|
||||||
#include <traintastic/enum/xpressnetinterfacetype.hpp>
|
#include <traintastic/enum/xpressnetinterfacetype.hpp>
|
||||||
#include <traintastic/enum/xpressnetserialinterfacetype.hpp>
|
#include <traintastic/enum/xpressnetserialinterfacetype.hpp>
|
||||||
|
#include "../settings/boardsettings.hpp"
|
||||||
|
|
||||||
#define GET_ENUM_VALUES(_type) \
|
#define GET_ENUM_VALUES(_type) \
|
||||||
if(enumName == EnumName<_type>::value) \
|
if(enumName == EnumName<_type>::value) \
|
||||||
@ -87,6 +88,7 @@ QString translateEnum(const QString& enumName, qint64 value)
|
|||||||
TRANSLATE_ENUM(XpressNetCommandStation)
|
TRANSLATE_ENUM(XpressNetCommandStation)
|
||||||
TRANSLATE_ENUM(XpressNetInterfaceType)
|
TRANSLATE_ENUM(XpressNetInterfaceType)
|
||||||
TRANSLATE_ENUM(XpressNetSerialInterfaceType)
|
TRANSLATE_ENUM(XpressNetSerialInterfaceType)
|
||||||
|
TRANSLATE_ENUM(BoardSettings::ColorScheme)
|
||||||
return enumName + "@" + QString::number(value);
|
return enumName + "@" + QString::number(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,6 @@ void ItemsEditWidget::buildForm()
|
|||||||
QVBoxLayout* left = new QVBoxLayout();
|
QVBoxLayout* left = new QVBoxLayout();
|
||||||
|
|
||||||
QToolBar* toolbar = new QToolBar(this);
|
QToolBar* toolbar = new QToolBar(this);
|
||||||
QAction* act;
|
|
||||||
|
|
||||||
if(m_methodAdd)
|
if(m_methodAdd)
|
||||||
toolbar->addAction(new MethodAction(Theme::getIcon("add"), *m_methodAdd, toolbar));
|
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);
|
connect(m_propertyItems, &ObjectVectorProperty::valueChanged, this, &ItemsEditWidget::itemsChanged);
|
||||||
itemsChanged();
|
itemsChanged();
|
||||||
connect(m_list, &QListWidget::currentItemChanged, this,
|
connect(m_list, &QListWidget::currentItemChanged, this,
|
||||||
[this](QListWidgetItem* current, QListWidgetItem* previous)
|
[this](QListWidgetItem* current, QListWidgetItem* /*previous*/)
|
||||||
{
|
{
|
||||||
if(current)
|
if(current)
|
||||||
if(auto* w = m_items.value(current->data(objectIdRole).toString()))
|
if(auto* w = m_items.value(current->data(objectIdRole).toString()))
|
||||||
|
|||||||
@ -96,7 +96,7 @@ void ObjectEditWidget::buildForm()
|
|||||||
ObjectProperty* property = static_cast<ObjectProperty*>(baseProperty);
|
ObjectProperty* property = static_cast<ObjectProperty*>(baseProperty);
|
||||||
if(contains(baseProperty->flags(), PropertyFlags::SubObject))
|
if(contains(baseProperty->flags(), PropertyFlags::SubObject))
|
||||||
{
|
{
|
||||||
QWidget* w = new ObjectEditWidget(property->objectId());
|
w = new ObjectEditWidget(property->objectId());
|
||||||
w->setWindowTitle(property->displayName());
|
w->setWindowTitle(property->displayName());
|
||||||
tabs.append(w);
|
tabs.append(w);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -51,9 +51,9 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
|
|||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
m_requestIdInputMonitor{Connection::invalidRequestId},
|
m_requestIdInputMonitor{Connection::invalidRequestId},
|
||||||
m_requestIdOutputKeyboard{Connection::invalidRequestId},
|
m_requestIdOutputKeyboard{Connection::invalidRequestId},
|
||||||
m_buttonAdd{nullptr},
|
|
||||||
m_object{object},
|
m_object{object},
|
||||||
m_toolbar{new QToolBar()},
|
m_toolbar{new QToolBar()},
|
||||||
|
m_buttonAdd{nullptr},
|
||||||
m_actionAdd{nullptr},
|
m_actionAdd{nullptr},
|
||||||
m_actionEdit{nullptr},
|
m_actionEdit{nullptr},
|
||||||
m_actionDelete{nullptr},
|
m_actionDelete{nullptr},
|
||||||
@ -106,12 +106,12 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
|
|||||||
m_object->connection()->cancelRequest(m_requestIdAdd);
|
m_object->connection()->cancelRequest(m_requestIdAdd);
|
||||||
|
|
||||||
m_requestIdAdd = method->call(
|
m_requestIdAdd = method->call(
|
||||||
[this](const ObjectPtr& object, Message::ErrorCode /*ec*/)
|
[this](const ObjectPtr& addedObject, Message::ErrorCode /*ec*/)
|
||||||
{
|
{
|
||||||
m_requestIdAdd = Connection::invalidRequestId;
|
m_requestIdAdd = Connection::invalidRequestId;
|
||||||
if(object)
|
if(addedObject)
|
||||||
{
|
{
|
||||||
MainWindow::instance->showObject(object);
|
MainWindow::instance->showObject(addedObject);
|
||||||
}
|
}
|
||||||
// TODO: show error
|
// TODO: show error
|
||||||
});
|
});
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "propertycombobox.hpp"
|
#include "propertycombobox.hpp"
|
||||||
|
#include <cassert>
|
||||||
#include "../network/property.hpp"
|
#include "../network/property.hpp"
|
||||||
#include "../utils/internalupdateholder.hpp"
|
#include "../utils/internalupdateholder.hpp"
|
||||||
#include "../utils/enum.hpp"
|
#include "../utils/enum.hpp"
|
||||||
@ -103,6 +104,15 @@ void PropertyComboBox::updateValues()
|
|||||||
setCurrentIndex(count() - 1);
|
setCurrentIndex(count() - 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ValueType::Invalid:
|
||||||
|
case ValueType::Boolean:
|
||||||
|
case ValueType::Float:
|
||||||
|
case ValueType::String:
|
||||||
|
case ValueType::Object:
|
||||||
|
case ValueType::Set:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -182,7 +182,10 @@ PropertyLuaCodeEdit::Highlighter::Highlighter(QTextDocument* parent) :
|
|||||||
m_rules.append(Rule(regex, Qt::magenta));
|
m_rules.append(Rule(regex, Qt::magenta));
|
||||||
|
|
||||||
const auto globals = {
|
const auto globals = {
|
||||||
QStringLiteral("\\log(?=\\.)"),
|
QStringLiteral("\\bmath(?=\\.)"),
|
||||||
|
QStringLiteral("\\bstring(?=\\.)"),
|
||||||
|
QStringLiteral("\\btable(?=\\.)"),
|
||||||
|
QStringLiteral("\\blog(?=\\.)"),
|
||||||
QStringLiteral("\\bworld(?=\\.)"),
|
QStringLiteral("\\bworld(?=\\.)"),
|
||||||
QStringLiteral("\\bset(?=\\.)"),
|
QStringLiteral("\\bset(?=\\.)"),
|
||||||
QStringLiteral("\\benum(?=\\.)"),
|
QStringLiteral("\\benum(?=\\.)"),
|
||||||
|
|||||||
@ -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());
|
m_speedoMeter->setEStop(m_emergencyStop->toBool());
|
||||||
connect(m_emergencyStop, &AbstractProperty::valueChangedBool, m_speedoMeter, &SpeedoMeterWidget::setEStop);
|
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")))
|
if(auto* p = dynamic_cast<ObjectProperty*>(m_object->getProperty("functions")))
|
||||||
{
|
{
|
||||||
m_functionsRequestId = m_object->connection()->getObject(p->objectId(),
|
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;
|
m_functionsRequestId = Connection::invalidRequestId;
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
|||||||
for(const QString& id : *items)
|
for(const QString& id : *items)
|
||||||
{
|
{
|
||||||
const int functionRequestId = functions->connection()->getObject(id,
|
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);
|
m_functionRequestIds.erase(i);
|
||||||
|
|
||||||
@ -263,9 +263,7 @@ ThrottleFunctionButton* ThrottleWidget::getFunctionButton(DecoderFunctionFunctio
|
|||||||
auto it = std::find_if(m_functionButtons.begin(), m_functionButtons.end(),
|
auto it = std::find_if(m_functionButtons.begin(), m_functionButtons.end(),
|
||||||
[function](const auto* btn)
|
[function](const auto* btn)
|
||||||
{
|
{
|
||||||
auto n = btn->number();
|
return btn->function() == function;
|
||||||
auto f = btn->function();
|
|
||||||
return f == function;
|
|
||||||
});
|
});
|
||||||
return (it != m_functionButtons.end()) ? *it : nullptr;
|
return (it != m_functionButtons.end()) ? *it : nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,6 +169,18 @@
|
|||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Lua **********************************************************************/
|
||||||
|
|
||||||
|
code.lua span.const
|
||||||
|
{
|
||||||
|
color: #4e004e;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.lua span.keyword
|
||||||
|
{
|
||||||
|
color: #000080;
|
||||||
|
}
|
||||||
|
|
||||||
/** MEDIA BREAKPOINTS ********************************************************/
|
/** MEDIA BREAKPOINTS ********************************************************/
|
||||||
|
|
||||||
@media screen and (max-width: 991px)
|
@media screen and (max-width: 991px)
|
||||||
|
|||||||
3
manual/traintasticmanual/en-us/lua.md
Normale Datei
3
manual/traintasticmanual/en-us/lua.md
Normale Datei
@ -0,0 +1,3 @@
|
|||||||
|
# Lua scripting {#lua}
|
||||||
|
|
||||||
|
TODO
|
||||||
20
manual/traintasticmanual/en-us/lua/enum/worldevent.md
Normale Datei
20
manual/traintasticmanual/en-us/lua/enum/worldevent.md
Normale Datei
@ -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` | |
|
||||||
13
manual/traintasticmanual/en-us/lua/enum/worldscale.md
Normale Datei
13
manual/traintasticmanual/en-us/lua/enum/worldscale.md
Normale Datei
@ -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 | |
|
||||||
4
manual/traintasticmanual/en-us/lua/enums.md
Normale Datei
4
manual/traintasticmanual/en-us/lua/enums.md
Normale Datei
@ -0,0 +1,4 @@
|
|||||||
|
# Enums {#lua-enums}
|
||||||
|
|
||||||
|
- [`world_event`](enum/worldevent.md)
|
||||||
|
- [`world_scale`](enum/worldscale.md)
|
||||||
59
manual/traintasticmanual/en-us/lua/globals.md
Normale Datei
59
manual/traintasticmanual/en-us/lua/globals.md
Normale Datei
@ -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`
|
||||||
15
manual/traintasticmanual/en-us/lua/object/board.md
Normale Datei
15
manual/traintasticmanual/en-us/lua/object/board.md
Normale Datei
@ -0,0 +1,15 @@
|
|||||||
|
# Board {#lua-object-board}
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
`string id`
|
||||||
|
|
||||||
|
`string name`
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
*None.*
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
*None.*
|
||||||
25
manual/traintasticmanual/en-us/lua/object/log.md
Normale Datei
25
manual/traintasticmanual/en-us/lua/object/log.md
Normale Datei
@ -0,0 +1,25 @@
|
|||||||
|
# Log {#lua-object-log}
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
*None.*
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
`debug(...)`
|
||||||
|
|
||||||
|
`info(...)`
|
||||||
|
|
||||||
|
`notice(...)`
|
||||||
|
|
||||||
|
`warning(...)`
|
||||||
|
|
||||||
|
`error(...)`
|
||||||
|
|
||||||
|
`critical(...)`
|
||||||
|
|
||||||
|
`fatal(...)`
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
*None.*
|
||||||
29
manual/traintasticmanual/en-us/lua/object/world.md
Normale Datei
29
manual/traintasticmanual/en-us/lua/object/world.md
Normale Datei
@ -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)`
|
||||||
5
manual/traintasticmanual/en-us/lua/objects.md
Normale Datei
5
manual/traintasticmanual/en-us/lua/objects.md
Normale Datei
@ -0,0 +1,5 @@
|
|||||||
|
# Objects {#lua-objects}
|
||||||
|
|
||||||
|
- [Board](object/board.md)
|
||||||
|
- [Log](object/log.md)
|
||||||
|
- [World](object/world.md)
|
||||||
14
manual/traintasticmanual/en-us/lua/set/worldstate.md
Normale Datei
14
manual/traintasticmanual/en-us/lua/set/worldstate.md
Normale Datei
@ -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` | |
|
||||||
3
manual/traintasticmanual/en-us/lua/sets.md
Normale Datei
3
manual/traintasticmanual/en-us/lua/sets.md
Normale Datei
@ -0,0 +1,3 @@
|
|||||||
|
# Sets {#lua-sets}
|
||||||
|
|
||||||
|
- [`world_state`](set/worldstate.md)
|
||||||
@ -59,4 +59,4 @@ TODO
|
|||||||
|
|
||||||
## C9999: *message* {#c9999}
|
## C9999: *message* {#c9999}
|
||||||
|
|
||||||
Custom critical message generated by a [Lua script](../scripting.md).
|
Custom critical message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -55,4 +55,4 @@ TODO
|
|||||||
|
|
||||||
## D9999: *message* {#d9999}
|
## D9999: *message* {#d9999}
|
||||||
|
|
||||||
Custom debug message generated by a [Lua script](../scripting.md).
|
Custom debug message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -105,4 +105,4 @@ TODO
|
|||||||
|
|
||||||
## E9999: *message* {#e9999}
|
## E9999: *message* {#e9999}
|
||||||
|
|
||||||
Custom error message generated by a [Lua script](../scripting.md).
|
Custom error message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -55,4 +55,4 @@ TODO
|
|||||||
|
|
||||||
## F9999: *message* {#f9999}
|
## F9999: *message* {#f9999}
|
||||||
|
|
||||||
Custom fatal message generated by a [Lua script](../scripting.md).
|
Custom fatal message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -35,4 +35,4 @@ TODO
|
|||||||
|
|
||||||
## I9999: *message* {#i9999}
|
## I9999: *message* {#i9999}
|
||||||
|
|
||||||
Custom info message generated by a [Lua script](../scripting.md).
|
Custom info message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -116,4 +116,4 @@ TODO
|
|||||||
|
|
||||||
## N9999: *message* {#n9999}
|
## N9999: *message* {#n9999}
|
||||||
|
|
||||||
Custom notice message generated by a [Lua script](../scripting.md).
|
Custom notice message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -35,4 +35,4 @@ If not, remapping decoder functions or using a different command station is the
|
|||||||
|
|
||||||
## W9999: *message* {#w9999}
|
## W9999: *message* {#w9999}
|
||||||
|
|
||||||
Custom warning message generated by a [Lua script](../scripting.md).
|
Custom warning message generated by a [Lua script](../lua.md).
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
# Scripting {#scripting}
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@ -60,7 +60,56 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chapter",
|
"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",
|
"type": "chapter",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import codecs
|
import codecs
|
||||||
import pycmarkgfm # pip3 install pycmarkgfm
|
import cmarkgfm # pip3 install cmarkgfm
|
||||||
from .builder import Builder
|
from .builder import Builder
|
||||||
|
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ class HTMLBuilder(Builder):
|
|||||||
|
|
||||||
def _file_to_html(self, page):
|
def _file_to_html(self, page):
|
||||||
with codecs.open(os.path.join(self._language_dir, page['markdown']), 'r', 'utf-8') as md:
|
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
|
# parse id
|
||||||
html = re.sub(r'<h([1-6])([^>]*)>(.*) {#([a-z0-9-]+)}</h\1>', r'<h\1\2 id="\4">\3</h\1>', html)
|
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:
|
# set target="_blank" for external links:
|
||||||
html = re.sub(r'<a([^>]+href="http(s|)://)', r'<a target="_blank"\1', html)
|
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
|
# change img title attribute to figcaption
|
||||||
html = re.sub(r'(<img[^>]+)title="([^">]*)"([^>]*>)',
|
html = re.sub(r'(<img[^>]+)title="([^">]*)"([^>]*>)',
|
||||||
lambda m:
|
lambda m:
|
||||||
@ -30,3 +33,9 @@ class HTMLBuilder(Builder):
|
|||||||
html)
|
html)
|
||||||
|
|
||||||
return 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>'
|
||||||
|
|||||||
@ -36,6 +36,15 @@ class HTMLSinglePageBuilder(HTMLBuilder):
|
|||||||
|
|
||||||
return html
|
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):
|
def build(self):
|
||||||
self._output_copy_files([
|
self._output_copy_files([
|
||||||
'css/pure-min.css',
|
'css/pure-min.css',
|
||||||
@ -46,12 +55,7 @@ class HTMLSinglePageBuilder(HTMLBuilder):
|
|||||||
toc = {'preface': [], 'chapter': [], 'appendix': []}
|
toc = {'preface': [], 'chapter': [], 'appendix': []}
|
||||||
manual_html = ''
|
manual_html = ''
|
||||||
for page in self._json:
|
for page in self._json:
|
||||||
page_html = self._file_to_html(page)
|
page_html = self._file_to_html(page) + self.subpages(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
|
|
||||||
|
|
||||||
m = re.findall(r'<h([1-2])([^>]*)>(.*?)</h\1>', page_html)
|
m = re.findall(r'<h([1-2])([^>]*)>(.*?)</h\1>', page_html)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
Board::Board(const std::weak_ptr<World>& world, std::string_view _id) :
|
Board::Board(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||||
IdObject(world, _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},
|
left{this, "left", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
||||||
top{this, "top", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
top{this, "top", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
||||||
right{this, "right", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
right{this, "right", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
||||||
@ -46,9 +46,12 @@ Board::Board(const std::weak_ptr<World>& world, std::string_view _id) :
|
|||||||
|
|
||||||
if(auto it = m_tiles.find(l); it != m_tiles.end())
|
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;
|
const TileRotate tileRotate = it->second->rotate;
|
||||||
|
|
||||||
|
if(it->second->tileId() == TileId::RailStraight && tileClassId == StraightRailTile::classId) // merge to bridge
|
||||||
|
{
|
||||||
if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y))
|
if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y))
|
||||||
{
|
{
|
||||||
tileClassId = Bridge90RailTile::classId;
|
tileClassId = Bridge90RailTile::classId;
|
||||||
@ -67,6 +70,16 @@ Board::Board(const std::weak_ptr<World>& world, std::string_view _id) :
|
|||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else if(it->second->tileId() == TileId::RailStraight && // replace straight by a straight with something extra
|
||||||
|
Tiles::canUpgradeStraightRail(tileClassId) &&
|
||||||
|
(tileRotate == rotate || (tileRotate + TileRotate::Deg180) == rotate) &&
|
||||||
|
deleteTile(x, y))
|
||||||
|
{
|
||||||
|
// tileClassId and rotate are ok :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
else if(!replace || !deleteTile(x, y))
|
else if(!replace || !deleteTile(x, y))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
class BufferStopRailTile : public RailTile
|
class BufferStopRailTile : public RailTile
|
||||||
{
|
{
|
||||||
CLASS_ID("board_tile.rail.bufferstop")
|
CLASS_ID("board_tile.rail.buffer_stop")
|
||||||
CREATE(BufferStopRailTile)
|
CREATE(BufferStopRailTile)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -52,3 +52,13 @@ std::shared_ptr<Tile> Tiles::create(const std::shared_ptr<World>& world, std::st
|
|||||||
IF_CLASSID_CREATE(TunnelRailTile)
|
IF_CLASSID_CREATE(TunnelRailTile)
|
||||||
return std::shared_ptr<Tile>();
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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 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
|
#endif
|
||||||
|
|||||||
56
server/src/core/abstractevent.cpp
Normale Datei
56
server/src/core/abstractevent.cpp
Normale Datei
@ -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);
|
||||||
|
}
|
||||||
59
server/src/core/abstractevent.hpp
Normale Datei
59
server/src/core/abstractevent.hpp
Normale Datei
@ -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
|
||||||
34
server/src/core/abstracteventhandler.cpp
Normale Datei
34
server/src/core/abstracteventhandler.cpp
Normale Datei
@ -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());
|
||||||
|
}
|
||||||
50
server/src/core/abstracteventhandler.hpp
Normale Datei
50
server/src/core/abstracteventhandler.hpp
Normale Datei
@ -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
|
||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -22,7 +22,8 @@
|
|||||||
|
|
||||||
#include "abstractmethod.hpp"
|
#include "abstractmethod.hpp"
|
||||||
|
|
||||||
AbstractMethod::AbstractMethod(Object& object, const std::string& name) :
|
AbstractMethod::AbstractMethod(Object& object, const std::string& name, MethodFlags flags)
|
||||||
InterfaceItem(object, name)
|
: InterfaceItem(object, name)
|
||||||
|
, m_flags{flags}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -24,13 +24,17 @@
|
|||||||
#define TRAINTASTIC_SERVER_CORE_ABSTRACTMETHOD_HPP
|
#define TRAINTASTIC_SERVER_CORE_ABSTRACTMETHOD_HPP
|
||||||
|
|
||||||
#include "interfaceitem.hpp"
|
#include "interfaceitem.hpp"
|
||||||
|
#include "methodflags.hpp"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "objectptr.hpp"
|
#include "argument.hpp"
|
||||||
|
|
||||||
class AbstractMethod : public InterfaceItem
|
class AbstractMethod : public InterfaceItem
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
const MethodFlags m_flags;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class MethodCallError : public std::runtime_error
|
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>;
|
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::size_t argumentCount() const = 0;
|
||||||
virtual std::vector<ValueType> argumentTypes() const = 0;
|
virtual std::vector<ValueType> argumentTypes() const = 0;
|
||||||
virtual ValueType resultType() const = 0;
|
virtual ValueType resultType() const = 0;
|
||||||
virtual Result call(const std::vector<Argument>& args) = 0;
|
virtual Result call(const Arguments& args) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -24,8 +24,9 @@
|
|||||||
#include "../core/idobject.hpp"
|
#include "../core/idobject.hpp"
|
||||||
#include "../world/worldloader.hpp"
|
#include "../world/worldloader.hpp"
|
||||||
|
|
||||||
AbstractObjectList::AbstractObjectList(Object& _parent, const std::string& parentPropertyName) :
|
AbstractObjectList::AbstractObjectList(Object& _parent, const std::string& parentPropertyName)
|
||||||
SubObject{_parent, parentPropertyName}
|
: SubObject{_parent, parentPropertyName}
|
||||||
|
, length{this, "length", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::NoScript}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "subobject.hpp"
|
#include "subobject.hpp"
|
||||||
#include "table.hpp"
|
#include "table.hpp"
|
||||||
|
#include "property.hpp"
|
||||||
|
|
||||||
class WorldSaver;
|
class WorldSaver;
|
||||||
|
|
||||||
@ -40,7 +41,11 @@ class AbstractObjectList : public SubObject, public Table
|
|||||||
virtual void setItems(const std::vector<ObjectPtr>& items) = 0;
|
virtual void setItems(const std::vector<ObjectPtr>& items) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
Property<uint32_t> length;
|
||||||
|
|
||||||
AbstractObjectList(Object& _parent, const std::string& parentPropertyName);
|
AbstractObjectList(Object& _parent, const std::string& parentPropertyName);
|
||||||
|
|
||||||
|
virtual ObjectPtr getObject(uint32_t index) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
35
server/src/core/argument.hpp
Normale Datei
35
server/src/core/argument.hpp
Normale Datei
@ -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
|
||||||
@ -43,6 +43,7 @@ class BaseProperty : public InterfaceItem
|
|||||||
assert(type != ValueType::Invalid);
|
assert(type != ValueType::Invalid);
|
||||||
assert(is_access_valid(flags));
|
assert(is_access_valid(flags));
|
||||||
assert(is_store_valid(flags));
|
assert(is_store_valid(flags));
|
||||||
|
assert(isScriptValid(flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
void changed();
|
void changed();
|
||||||
@ -63,6 +64,17 @@ class BaseProperty : public InterfaceItem
|
|||||||
return (m_flags & PropertyFlagsStoreMask) == PropertyFlags::StoreState;
|
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
|
PropertyFlags flags() const
|
||||||
{
|
{
|
||||||
return m_flags;
|
return m_flags;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -34,6 +34,12 @@ TableModelPtr ControllerListBase::getModel()
|
|||||||
return std::make_shared<ControllerListBaseTableModel>(*this);
|
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)
|
void ControllerListBase::add(ObjectPtr controller)
|
||||||
{
|
{
|
||||||
assert(controller);
|
assert(controller);
|
||||||
|
|||||||
@ -54,6 +54,8 @@ class ControllerListBase : public AbstractObjectList
|
|||||||
ControllerListBase(Object& _parent, const std::string& parentPropertyName);
|
ControllerListBase(Object& _parent, const std::string& parentPropertyName);
|
||||||
|
|
||||||
TableModelPtr getModel() final;
|
TableModelPtr getModel() final;
|
||||||
|
|
||||||
|
ObjectPtr getObject(uint32_t index) final;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
83
server/src/core/event.hpp
Normale Datei
83
server/src/core/event.hpp
Normale Datei
@ -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
|
||||||
37
server/src/core/eventflags.hpp
Normale Datei
37
server/src/core/eventflags.hpp
Normale Datei
@ -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
|
||||||
@ -27,7 +27,7 @@
|
|||||||
IdObject::IdObject(const std::weak_ptr<World>& world, std::string_view _id) :
|
IdObject::IdObject(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||||
Object{},
|
Object{},
|
||||||
m_world{world},
|
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)
|
[this](const std::string& value)
|
||||||
{
|
{
|
||||||
idChanged(value);
|
idChanged(value);
|
||||||
|
|||||||
@ -44,7 +44,7 @@ template<std::size_t N, class... A>
|
|||||||
using getArgumentType = typename std::tuple_element<N, std::tuple<A...>>::type;
|
using getArgumentType = typename std::tuple_element<N, std::tuple<A...>>::type;
|
||||||
|
|
||||||
template<std::size_t N, class... A>
|
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...>>>;
|
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;
|
std::function<R(A...)> m_function;
|
||||||
|
|
||||||
public:
|
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) :
|
Method(Object& object, const std::string& name, std::function<R(A...)> function) :
|
||||||
AbstractMethod(object, name),
|
AbstractMethod(object, name),
|
||||||
m_function{std::move(function)}
|
m_function{std::move(function)}
|
||||||
|
|||||||
36
server/src/core/methodflags.hpp
Normale Datei
36
server/src/core/methodflags.hpp
Normale Datei
@ -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
|
||||||
@ -35,6 +35,7 @@
|
|||||||
static constexpr std::string_view classId = id; \
|
static constexpr std::string_view classId = id; \
|
||||||
std::string_view getClassId() const override { return classId; }
|
std::string_view getClassId() const override { return classId; }
|
||||||
|
|
||||||
|
template<class... Args> class Event;
|
||||||
class AbstractMethod;
|
class AbstractMethod;
|
||||||
class BaseProperty;
|
class BaseProperty;
|
||||||
class AbstractProperty;
|
class AbstractProperty;
|
||||||
@ -55,6 +56,12 @@ class Object : public std::enable_shared_from_this<Object>
|
|||||||
protected:
|
protected:
|
||||||
InterfaceItems m_interfaceItems;
|
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; }
|
inline bool dying() const noexcept { return m_dying; }
|
||||||
virtual void destroying() {}
|
virtual void destroying() {}
|
||||||
virtual void load(WorldLoader& loader, const nlohmann::json& data);
|
virtual void load(WorldLoader& loader, const nlohmann::json& data);
|
||||||
|
|||||||
@ -76,18 +76,15 @@ class ObjectList : public AbstractObjectList
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Property<uint32_t> length;
|
|
||||||
|
|
||||||
ObjectList(Object& _parent, const std::string& parentPropertyName) :
|
ObjectList(Object& _parent, const std::string& parentPropertyName) :
|
||||||
AbstractObjectList{_parent, parentPropertyName},
|
AbstractObjectList{_parent, parentPropertyName}
|
||||||
length{this, "length", 0, PropertyFlags::ReadOnly}
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const_iterator begin() const noexcept { return m_items.begin(); }
|
inline const_iterator begin() const noexcept { return m_items.begin(); }
|
||||||
inline const_iterator end() const noexcept { return m_items.end(); }
|
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());
|
assert(index < m_items.size());
|
||||||
return std::static_pointer_cast<Object>(m_items[index]);
|
return std::static_pointer_cast<Object>(m_items[index]);
|
||||||
|
|||||||
@ -260,7 +260,7 @@ bool Session::processMessage(const Message& message)
|
|||||||
const ValueType resultType = message.read<ValueType>();
|
const ValueType resultType = message.read<ValueType>();
|
||||||
const uint8_t argumentCount = message.read<uint8_t>();
|
const uint8_t argumentCount = message.read<uint8_t>();
|
||||||
|
|
||||||
std::vector<AbstractMethod::Argument> args;
|
Arguments args;
|
||||||
for(uint8_t i = 0; i < argumentCount; i++)
|
for(uint8_t i = 0; i < argumentCount; i++)
|
||||||
{
|
{
|
||||||
switch(message.read<ValueType>())
|
switch(message.read<ValueType>())
|
||||||
|
|||||||
47
server/src/lua/checkarguments.hpp
Normale Datei
47
server/src/lua/checkarguments.hpp
Normale Datei
@ -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
|
||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -23,6 +23,7 @@
|
|||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
#include "push.hpp"
|
#include "push.hpp"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
|
#include "checkarguments.hpp"
|
||||||
|
|
||||||
#include "../board/board.hpp"
|
#include "../board/board.hpp"
|
||||||
#include "../board/boardlist.hpp"
|
#include "../board/boardlist.hpp"
|
||||||
@ -96,78 +97,132 @@ static bool isInstance(const ::ObjectPtr& object)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
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>);
|
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>;
|
*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_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);
|
lua_settable(L, -3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Class::registerValues(lua_State* L)
|
void Class::registerValues(lua_State* L)
|
||||||
{
|
{
|
||||||
assert(lua_istable(L, -1));
|
assert(lua_istable(L, -1)); // sandboxes global class
|
||||||
|
|
||||||
setField<Board>(L, "BOARD");
|
lua_newtable(L); // global
|
||||||
setField<BoardList>(L, "BOARD_LIST");
|
lua_setglobal(L, metaTableName);
|
||||||
|
|
||||||
setField<StraightRailTile>(L, "STRAIGHT_RAIL_TILE");
|
registerValue<Board>(L, "BOARD");
|
||||||
setField<TunnelRailTile>(L, "TUNNEL_RAIL_TILE");
|
registerValue<BoardList>(L, "BOARD_LIST");
|
||||||
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");
|
|
||||||
|
|
||||||
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");
|
registerValue<Clock>(L, "CLOCK");
|
||||||
setField<DecoderList>(L, "DECODER_LIST");
|
|
||||||
setField<Decoder>(L, "DECODER");
|
|
||||||
setField<DecoderFunctions>(L, "DECODER_FUNCTIONS");
|
|
||||||
|
|
||||||
setField<Input>(L, "INPUT");
|
registerValue<DecoderFunction>(L, "DECODER_FUNCTION");
|
||||||
setField<InputList>(L, "INPUT_LIST");
|
registerValue<DecoderList>(L, "DECODER_LIST");
|
||||||
|
registerValue<Decoder>(L, "DECODER");
|
||||||
|
registerValue<DecoderFunctions>(L, "DECODER_FUNCTIONS");
|
||||||
|
|
||||||
setField<Output>(L, "OUTPUT");
|
registerValue<Input>(L, "INPUT");
|
||||||
setField<OutputList>(L, "OUTPUT_LIST");
|
registerValue<InputList>(L, "INPUT_LIST");
|
||||||
|
|
||||||
setField<RailVehicleList>(L, "RAIL_VEHICLE_LIST");
|
registerValue<Output>(L, "OUTPUT");
|
||||||
setField<Locomotive>(L, "LOCOMOTIVE");
|
registerValue<OutputList>(L, "OUTPUT_LIST");
|
||||||
setField<FreightCar>(L, "FREIGHT_CAR");
|
|
||||||
|
|
||||||
setField<Train>(L, "TRAIN");
|
registerValue<RailVehicleList>(L, "RAIL_VEHICLE_LIST");
|
||||||
setField<TrainList>(L, "TRAIN_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)
|
lua_getglobal(L, metaTableName);
|
||||||
errorExpectedNArgumentsGotN(L, 2, lua_gettop(L));
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
#ifndef TRAINTASTIC_SERVER_LUA_CLASS_HPP
|
#ifndef TRAINTASTIC_SERVER_LUA_CLASS_HPP
|
||||||
#define TRAINTASTIC_SERVER_LUA_CLASS_HPP
|
#define TRAINTASTIC_SERVER_LUA_CLASS_HPP
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
#include "../core/objectptr.hpp"
|
#include "../core/objectptr.hpp"
|
||||||
|
|
||||||
@ -32,7 +33,19 @@ struct Class
|
|||||||
{
|
{
|
||||||
static void registerValues(lua_State* L);
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,15 @@
|
|||||||
|
|
||||||
namespace Lua {
|
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>
|
template<typename T>
|
||||||
struct Enum
|
struct Enum
|
||||||
{
|
{
|
||||||
@ -53,11 +62,7 @@ struct Enum
|
|||||||
|
|
||||||
static void push(lua_State* L, T value)
|
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
|
pushEnum(L, EnumName<T>::value, static_cast<lua_Integer>(value));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __tostring(lua_State* L)
|
static int __tostring(lua_State* L)
|
||||||
|
|||||||
@ -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 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 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 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 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 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(); }
|
[[noreturn]] inline void errorInternal(lua_State* L) { luaL_error(L, "internal error"); abort(); }
|
||||||
|
|
||||||
|
|||||||
134
server/src/lua/event.cpp
Normale Datei
134
server/src/lua/event.cpp
Normale Datei
@ -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
53
server/src/lua/event.hpp
Normale Datei
@ -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
|
||||||
128
server/src/lua/eventhandler.cpp
Normale Datei
128
server/src/lua/eventhandler.cpp
Normale Datei
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
server/src/lua/eventhandler.hpp
Normale Datei
51
server/src/lua/eventhandler.hpp
Normale Datei
@ -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
|
||||||
@ -26,6 +26,7 @@
|
|||||||
#include "script.hpp"
|
#include "script.hpp"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
#include "method.hpp"
|
#include "method.hpp"
|
||||||
|
#include "event.hpp"
|
||||||
#include "../log/log.hpp"
|
#include "../log/log.hpp"
|
||||||
|
|
||||||
namespace Lua {
|
namespace Lua {
|
||||||
@ -79,6 +80,8 @@ int Log::log(lua_State* L, LogMessage code)
|
|||||||
message += object->getClassId();
|
message += object->getClassId();
|
||||||
else if(Method::test(L, i))
|
else if(Method::test(L, i))
|
||||||
message += "method";
|
message += "method";
|
||||||
|
else if(Event::test(L, i))
|
||||||
|
message += "event";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lua_getglobal(L, "tostring");
|
lua_getglobal(L, "tostring");
|
||||||
|
|||||||
@ -91,7 +91,7 @@ int Method::__call(lua_State* L)
|
|||||||
if(lua_gettop(L) - 1 != argc)
|
if(lua_gettop(L) - 1 != argc)
|
||||||
errorExpectedNArgumentsGotN(L, argc, lua_gettop(L) - 1);
|
errorExpectedNArgumentsGotN(L, argc, lua_gettop(L) - 1);
|
||||||
|
|
||||||
std::vector<AbstractMethod::Argument> args;
|
Arguments args;
|
||||||
args.reserve(argc);
|
args.reserve(argc);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,33 +25,61 @@
|
|||||||
#include "check.hpp"
|
#include "check.hpp"
|
||||||
#include "to.hpp"
|
#include "to.hpp"
|
||||||
#include "method.hpp"
|
#include "method.hpp"
|
||||||
|
#include "event.hpp"
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
#include "../core/object.hpp"
|
#include "../core/object.hpp"
|
||||||
#include "../core/abstractproperty.hpp"
|
#include "../core/abstractproperty.hpp"
|
||||||
#include "../core/abstractmethod.hpp"
|
#include "../core/abstractmethod.hpp"
|
||||||
|
#include "../core/abstractevent.hpp"
|
||||||
|
|
||||||
namespace Lua {
|
namespace Lua {
|
||||||
|
|
||||||
ObjectPtr Object::check(lua_State* L, int index)
|
ObjectPtr Object::check(lua_State* L, int index)
|
||||||
{
|
{
|
||||||
ObjectPtrWeak& data = **static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableName));
|
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
|
||||||
if(ObjectPtr object = data.lock())
|
if(!data)
|
||||||
|
data = static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableName));
|
||||||
|
|
||||||
|
if(ObjectPtr object = (**data).lock())
|
||||||
return object;
|
return object;
|
||||||
else
|
else
|
||||||
errorDeadObject(L);
|
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)
|
ObjectPtr Object::test(lua_State* L, int index)
|
||||||
{
|
{
|
||||||
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableName));
|
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableName));
|
||||||
if(!data)
|
if(!data)
|
||||||
return ObjectPtr();
|
data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
|
||||||
|
|
||||||
|
if(!data)
|
||||||
|
return {};
|
||||||
else if(ObjectPtr object = (**data).lock())
|
else if(ObjectPtr object = (**data).lock())
|
||||||
return object;
|
return object;
|
||||||
else
|
else
|
||||||
errorDeadObject(L);
|
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)
|
void Object::push(lua_State* L, const ObjectPtr& value)
|
||||||
{
|
{
|
||||||
if(value)
|
if(value)
|
||||||
@ -62,6 +90,9 @@ void Object::push(lua_State* L, const ObjectPtr& value)
|
|||||||
{
|
{
|
||||||
lua_pop(L, 1); // remove nil
|
lua_pop(L, 1); // remove nil
|
||||||
*static_cast<ObjectPtrWeak**>(lua_newuserdata(L, sizeof(ObjectPtrWeak*))) = new ObjectPtrWeak(value);
|
*static_cast<ObjectPtrWeak**>(lua_newuserdata(L, sizeof(ObjectPtrWeak*))) = new ObjectPtrWeak(value);
|
||||||
|
if(dynamic_cast<AbstractObjectList*>(value.get()))
|
||||||
|
luaL_setmetatable(L, metaTableNameList);
|
||||||
|
else
|
||||||
luaL_setmetatable(L, metaTableName);
|
luaL_setmetatable(L, metaTableName);
|
||||||
lua_pushvalue(L, -1); // copy userdata on stack
|
lua_pushvalue(L, -1); // copy userdata on stack
|
||||||
lua_rawsetp(L, -3, value.get()); // add object to table
|
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_setfield(L, -2, "__newindex");
|
||||||
lua_pop(L, 1);
|
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:
|
// weak table for object userdata:
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
lua_newtable(L); // metatable
|
lua_newtable(L); // metatable
|
||||||
@ -104,13 +147,30 @@ int Object::__gc(lua_State* L)
|
|||||||
int Object::__index(lua_State* L)
|
int Object::__index(lua_State* L)
|
||||||
{
|
{
|
||||||
ObjectPtr object{check(L, 1)};
|
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)};
|
std::string_view name{to<std::string_view>(L, 2)};
|
||||||
|
|
||||||
if(InterfaceItem* item = object->getItem(name))
|
if(InterfaceItem* item = object->getItem(name))
|
||||||
{
|
{
|
||||||
// TODO: test scriptable
|
|
||||||
|
|
||||||
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(item))
|
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(item))
|
||||||
|
{
|
||||||
|
if(property->isScriptReadable())
|
||||||
{
|
{
|
||||||
switch(property->type())
|
switch(property->type())
|
||||||
{
|
{
|
||||||
@ -118,7 +178,12 @@ int Object::__index(lua_State* L)
|
|||||||
Lua::push(L, property->toBool());
|
Lua::push(L, property->toBool());
|
||||||
break;
|
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:
|
case ValueType::Integer:
|
||||||
Lua::push(L, property->toInt64());
|
Lua::push(L, property->toInt64());
|
||||||
@ -136,15 +201,35 @@ int Object::__index(lua_State* L)
|
|||||||
push(L, property->toObject());
|
push(L, property->toObject());
|
||||||
break;
|
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:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
else if(AbstractMethod* method = dynamic_cast<AbstractMethod*>(item))
|
else if(AbstractMethod* method = dynamic_cast<AbstractMethod*>(item))
|
||||||
{
|
{
|
||||||
|
if(method->isScriptCallable())
|
||||||
Method::push(L, *method);
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -165,9 +250,7 @@ int Object::__newindex(lua_State* L)
|
|||||||
|
|
||||||
if(AbstractProperty* property = object->getProperty(name))
|
if(AbstractProperty* property = object->getProperty(name))
|
||||||
{
|
{
|
||||||
// TODO: test scriptable
|
if(!property->isScriptWriteable() || !property->isWriteable())
|
||||||
|
|
||||||
if(!property->isWriteable())
|
|
||||||
errorCantSetReadOnlyProperty(L);
|
errorCantSetReadOnlyProperty(L);
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -209,4 +292,13 @@ int Object::__newindex(lua_State* L)
|
|||||||
errorCantSetNonExistingProperty(L);
|
errorCantSetNonExistingProperty(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Object::__len(lua_State* L)
|
||||||
|
{
|
||||||
|
auto list{checkList(L, 1)};
|
||||||
|
|
||||||
|
Lua::push(L, list->length.value());
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "../core/objectptr.hpp"
|
#include "../core/objectptr.hpp"
|
||||||
|
#include "../core/abstractobjectlist.hpp"
|
||||||
|
|
||||||
namespace Lua {
|
namespace Lua {
|
||||||
|
|
||||||
@ -35,12 +36,17 @@ class Object
|
|||||||
static int __gc(lua_State* L);
|
static int __gc(lua_State* L);
|
||||||
static int __index(lua_State* L);
|
static int __index(lua_State* L);
|
||||||
static int __newindex(lua_State* L);
|
static int __newindex(lua_State* L);
|
||||||
|
static int __len(lua_State* L);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr char const* metaTableName = "object";
|
static constexpr char const* metaTableName = "object";
|
||||||
|
static constexpr char const* metaTableNameList = "object_list";
|
||||||
|
|
||||||
static ObjectPtr check(lua_State* L, int index);
|
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 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);
|
static void push(lua_State* L, const ObjectPtr& value);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -24,22 +24,104 @@
|
|||||||
#include "push.hpp"
|
#include "push.hpp"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
#include "method.hpp"
|
#include "method.hpp"
|
||||||
|
#include "event.hpp"
|
||||||
|
#include "eventhandler.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
|
#include "to.hpp"
|
||||||
|
#include "type.hpp"
|
||||||
|
#include <version.hpp>
|
||||||
#include <traintastic/utils/str.hpp>
|
#include <traintastic/utils/str.hpp>
|
||||||
#include <traintastic/codename.hpp>
|
#include <traintastic/codename.hpp>
|
||||||
#include "../world/world.hpp"
|
#include "../world/world.hpp"
|
||||||
#include "../enum/decoderprotocol.hpp"
|
#include "../enum/decoderprotocol.hpp"
|
||||||
#include "../enum/direction.hpp"
|
#include "../enum/direction.hpp"
|
||||||
#include "../enum/worldevent.hpp"
|
#include "../enum/worldevent.hpp"
|
||||||
|
#include "../enum/worldscale.hpp"
|
||||||
#include "../set/worldstate.hpp"
|
#include "../set/worldstate.hpp"
|
||||||
|
|
||||||
#define LUA_SANDBOX "_sandbox"
|
#define LUA_SANDBOX "_sandbox"
|
||||||
|
#define LUA_SANDBOX_GLOBALS "_sandbox_globals"
|
||||||
|
|
||||||
#define ADD_GLOBAL_TO_SANDBOX(x) \
|
constexpr std::array<std::string_view, 23> readOnlyGlobals = {{
|
||||||
lua_pushliteral(L, x); \
|
// Lua baselib:
|
||||||
lua_getglobal(L, x); \
|
"assert",
|
||||||
lua_settable(L, -3);
|
"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 {
|
namespace Lua {
|
||||||
|
|
||||||
@ -49,15 +131,33 @@ void Sandbox::close(lua_State* L)
|
|||||||
lua_close(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)
|
SandboxPtr Sandbox::create(Script& script)
|
||||||
{
|
{
|
||||||
lua_State* L = luaL_newstate();
|
lua_State* L = luaL_newstate();
|
||||||
|
|
||||||
// load Lua baselib:
|
|
||||||
lua_pushcfunction(L, luaopen_base);
|
|
||||||
lua_pushliteral(L, "");
|
|
||||||
lua_call(L, 1, 0);
|
|
||||||
|
|
||||||
// create state data:
|
// create state data:
|
||||||
*static_cast<StateData**>(lua_getextraspace(L)) = new StateData(script);
|
*static_cast<StateData**>(lua_getextraspace(L)) = new StateData(script);
|
||||||
|
|
||||||
@ -65,27 +165,67 @@ SandboxPtr Sandbox::create(Script& script)
|
|||||||
Enum<DecoderProtocol>::registerType(L);
|
Enum<DecoderProtocol>::registerType(L);
|
||||||
Enum<Direction>::registerType(L);
|
Enum<Direction>::registerType(L);
|
||||||
Enum<WorldEvent>::registerType(L);
|
Enum<WorldEvent>::registerType(L);
|
||||||
|
Enum<WorldScale>::registerType(L);
|
||||||
Set<WorldState>::registerType(L);
|
Set<WorldState>::registerType(L);
|
||||||
Object::registerType(L);
|
Object::registerType(L);
|
||||||
Method::registerType(L);
|
Method::registerType(L);
|
||||||
|
Event::registerType(L);
|
||||||
|
|
||||||
// setup sandbox:
|
// setup sandbox:
|
||||||
lua_newtable(L);
|
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:
|
// setup globals:
|
||||||
ADD_GLOBAL_TO_SANDBOX("assert")
|
lua_newtable(L);
|
||||||
ADD_GLOBAL_TO_SANDBOX("type")
|
|
||||||
ADD_GLOBAL_TO_SANDBOX("pairs")
|
// add standard Lua lib functions and extensions/replacements:
|
||||||
ADD_GLOBAL_TO_SANDBOX("ipairs")
|
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:
|
// set VERSION:
|
||||||
lua_pushstring(L, STR(VERSION));
|
lua_pushstring(L, TRAINTASTIC_VERSION);
|
||||||
lua_setfield(L, -2, "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
|
// set CODENAME
|
||||||
lua_pushstring(L, TRAINTASTIC_CODENAME);
|
lua_pushstring(L, TRAINTASTIC_CODENAME);
|
||||||
lua_setfield(L, -2, "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:
|
// add world:
|
||||||
push(L, std::static_pointer_cast<::Object>(script.world().lock()));
|
push(L, std::static_pointer_cast<::Object>(script.world().lock()));
|
||||||
lua_setfield(L, -2, "world");
|
lua_setfield(L, -2, "world");
|
||||||
@ -94,10 +234,6 @@ SandboxPtr Sandbox::create(Script& script)
|
|||||||
Log::push(L);
|
Log::push(L);
|
||||||
lua_setfield(L, -2, "log");
|
lua_setfield(L, -2, "log");
|
||||||
|
|
||||||
// add is_instance function:
|
|
||||||
lua_pushcfunction(L, Class::isInstance);
|
|
||||||
lua_setfield(L, -2, "is_instance");
|
|
||||||
|
|
||||||
// add class types:
|
// add class types:
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
Class::registerValues(L);
|
Class::registerValues(L);
|
||||||
@ -109,6 +245,7 @@ SandboxPtr Sandbox::create(Script& script)
|
|||||||
Enum<DecoderProtocol>::registerValues(L);
|
Enum<DecoderProtocol>::registerValues(L);
|
||||||
Enum<Direction>::registerValues(L);
|
Enum<Direction>::registerValues(L);
|
||||||
Enum<WorldEvent>::registerValues(L);
|
Enum<WorldEvent>::registerValues(L);
|
||||||
|
Enum<WorldScale>::registerValues(L);
|
||||||
ReadOnlyTable::wrap(L, -1);
|
ReadOnlyTable::wrap(L, -1);
|
||||||
lua_setfield(L, -2, "enum");
|
lua_setfield(L, -2, "enum");
|
||||||
|
|
||||||
@ -118,12 +255,11 @@ SandboxPtr Sandbox::create(Script& script)
|
|||||||
ReadOnlyTable::wrap(L, -1);
|
ReadOnlyTable::wrap(L, -1);
|
||||||
lua_setfield(L, -2, "set");
|
lua_setfield(L, -2, "set");
|
||||||
|
|
||||||
// let global _G point to itself:
|
// let global _G point to the sandbox:
|
||||||
lua_pushliteral(L, "_G");
|
lua_getglobal(L, LUA_SANDBOX);
|
||||||
lua_pushvalue(L, -2);
|
lua_setfield(L, -2, "_G");
|
||||||
lua_settable(L, -3);
|
|
||||||
|
|
||||||
lua_setglobal(L, LUA_SANDBOX);
|
lua_setglobal(L, LUA_SANDBOX_GLOBALS);
|
||||||
|
|
||||||
return SandboxPtr(L, close);
|
return SandboxPtr(L, close);
|
||||||
}
|
}
|
||||||
@ -135,7 +271,7 @@ Sandbox::StateData& Sandbox::getStateData(lua_State* L)
|
|||||||
|
|
||||||
int Sandbox::getGlobal(lua_State* L, const char* name)
|
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);
|
lua_pushstring(L, name);
|
||||||
const int type = lua_gettable(L, -2); // get item
|
const int type = lua_gettable(L, -2); // get item
|
||||||
lua_insert(L, lua_gettop(L) - 1); // swap item and sandbox on the stack
|
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);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,11 +24,15 @@
|
|||||||
#define TRAINTASTIC_SERVER_LUA_SANDBOX_HPP
|
#define TRAINTASTIC_SERVER_LUA_SANDBOX_HPP
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
|
|
||||||
namespace Lua {
|
namespace Lua {
|
||||||
|
|
||||||
class Script;
|
class Script;
|
||||||
|
class EventHandler;
|
||||||
|
|
||||||
using SandboxPtr = std::unique_ptr<lua_State, void(*)(lua_State*)>;
|
using SandboxPtr = std::unique_ptr<lua_State, void(*)(lua_State*)>;
|
||||||
|
|
||||||
@ -36,23 +40,66 @@ class Sandbox
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
static void close(lua_State* L);
|
static void close(lua_State* L);
|
||||||
|
static int __index(lua_State* L);
|
||||||
|
static int __newindex(lua_State* L);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class StateData
|
class StateData
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
Script& m_script;
|
Script& m_script;
|
||||||
|
lua_Integer m_eventHandlerId;
|
||||||
|
std::map<lua_Integer, std::shared_ptr<EventHandler>> m_eventHandlers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StateData(Script& script) :
|
StateData(Script& script)
|
||||||
m_script{script}
|
: m_script{script}
|
||||||
|
, m_eventHandlerId{1}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~StateData();
|
||||||
|
|
||||||
inline Script& script() const
|
inline Script& script() const
|
||||||
{
|
{
|
||||||
return m_script;
|
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);
|
static SandboxPtr create(Script& script);
|
||||||
|
|||||||
@ -116,19 +116,6 @@ void Script::worldEvent(WorldState worldState, WorldEvent worldEvent)
|
|||||||
IdObject::worldEvent(worldState, worldEvent);
|
IdObject::worldEvent(worldState, worldEvent);
|
||||||
|
|
||||||
updateEnabled();
|
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()
|
void Script::updateEnabled()
|
||||||
|
|||||||
@ -49,6 +49,23 @@ struct set_values
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
constexpr auto set_values_v = set_values<T>::value;
|
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>
|
template<typename T>
|
||||||
struct Set
|
struct Set
|
||||||
{
|
{
|
||||||
@ -57,32 +74,20 @@ struct Set
|
|||||||
|
|
||||||
static T check(lua_State* L, int index)
|
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)
|
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)
|
if(data)
|
||||||
value = *data;
|
value = static_cast<T>(*data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void push(lua_State* L, T value)
|
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
|
pushSet(L, set_name_v<T>, static_cast<lua_Integer>(value));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __add(lua_State* L)
|
static int __add(lua_State* L)
|
||||||
|
|||||||
80
server/src/lua/type.cpp
Normale Datei
80
server/src/lua/type.cpp
Normale Datei
@ -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
34
server/src/lua/type.hpp
Normale Datei
@ -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
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren