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
|
||||
|
||||
- name: Install python packages
|
||||
run: sudo pip3 install pycmarkgfm
|
||||
run: sudo pip3 install cmarkgfm
|
||||
|
||||
- name: Build 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)
|
||||
- Manual:
|
||||
- Python 3.6+ (older versions untested)
|
||||
- pycmarkgfm (`pip3 install pycmarkgfm`)
|
||||
- cmarkgfm (`pip3 install cmarkgfm`)
|
||||
|
||||
Note: When cloning the source from git, git-lfs is required.
|
||||
|
||||
|
||||
@ -59,6 +59,7 @@ constexpr QRect updateTileRect(const int x, const int y, const int w, const int
|
||||
|
||||
BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_colorScheme{&BoardColorScheme::dark},
|
||||
m_board{board},
|
||||
m_boardLeft{board.board().getProperty("left")},
|
||||
m_boardTop{board.board().getProperty("top")},
|
||||
@ -84,11 +85,12 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||
if(Q_LIKELY(m_boardBottom))
|
||||
connect(m_boardBottom, &AbstractProperty::valueChanged, this, &BoardAreaWidget::updateMinimumSize);
|
||||
|
||||
connect(&BoardSettings::instance(), &SettingsBase::changed, this, qOverload<>(&QWidget::update));
|
||||
connect(&BoardSettings::instance(), &SettingsBase::changed, this, &BoardAreaWidget::settingsChanged);
|
||||
|
||||
for(const auto& [l, object] : m_board.board().tileObjects())
|
||||
tileObjectAdded(l.x, l.y, object);
|
||||
|
||||
settingsChanged();
|
||||
updateMinimumSize();
|
||||
}
|
||||
|
||||
@ -345,12 +347,15 @@ void BoardAreaWidget::mouseMoveEvent(QMouseEvent* event)
|
||||
|
||||
case MouseMoveAction::ResizeTile:
|
||||
update(updateTileRect(
|
||||
m_mouseMoveHideTileLocation.x,
|
||||
m_mouseMoveHideTileLocation.y,
|
||||
m_mouseMoveHideTileLocation.x - originX,
|
||||
m_mouseMoveHideTileLocation.y - originY,
|
||||
std::max(1, 1 + std::max(old.x, tl.x) - m_mouseMoveHideTileLocation.x),
|
||||
std::max(1, 1 + std::max(old.y, tl.y) - m_mouseMoveHideTileLocation.y),
|
||||
tileSize));
|
||||
break;
|
||||
|
||||
case MouseMoveAction::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -374,8 +379,9 @@ void BoardAreaWidget::wheelEvent(QWheelEvent* event)
|
||||
|
||||
void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
assert(m_colorScheme);
|
||||
|
||||
const bool showBlockSensorStates = BoardSettings::instance().showBlockSensorStates;
|
||||
const QColor backgroundColor = TilePainter::backgroundColor;
|
||||
const QColor backgroundColor50{0x10, 0x10, 0x10, 0x80};
|
||||
const QColor backgroundColorError50{0xff, 0x00, 0x00, 0x80};
|
||||
const QColor gridColor{0x40, 0x40, 0x40};
|
||||
@ -388,7 +394,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
|
||||
const QRect viewport = rectToViewport(event->rect(), gridSize);
|
||||
|
||||
painter.fillRect(viewport, backgroundColor);
|
||||
painter.fillRect(viewport, m_colorScheme->background);
|
||||
|
||||
// draw grid:
|
||||
switch(m_grid)
|
||||
@ -415,7 +421,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// draw tiles:
|
||||
TilePainter tilePainter{painter, tileSize};
|
||||
TilePainter tilePainter{painter, tileSize, *m_colorScheme};
|
||||
|
||||
const int tileOriginX = boardLeft();
|
||||
const int tileOriginY = boardTop();
|
||||
@ -531,9 +537,30 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MouseMoveAction::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BoardAreaWidget::settingsChanged()
|
||||
{
|
||||
const auto& s = BoardSettings::instance();
|
||||
|
||||
switch(s.colorScheme.value())
|
||||
{
|
||||
case BoardSettings::ColorScheme::Dark:
|
||||
m_colorScheme = &BoardColorScheme::dark;
|
||||
break;
|
||||
|
||||
case BoardSettings::ColorScheme::Light:
|
||||
m_colorScheme = &BoardColorScheme::light;
|
||||
break;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void BoardAreaWidget::updateMinimumSize()
|
||||
{
|
||||
const int tileSize = getTileSize() - 1;
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include <traintastic/enum/signalaspect.hpp>
|
||||
#include <traintastic/enum/tristate.hpp>
|
||||
#include <traintastic/enum/turnoutposition.hpp>
|
||||
#include "boardcolorscheme.hpp"
|
||||
#include "../network/abstractproperty.hpp"
|
||||
#include "../network/objectptr.hpp"
|
||||
|
||||
@ -57,6 +58,9 @@ class BoardAreaWidget : public QWidget
|
||||
ResizeTile,
|
||||
};
|
||||
|
||||
private:
|
||||
const BoardColorScheme* m_colorScheme;
|
||||
|
||||
protected:
|
||||
static constexpr int boardMargin = 1; // tile
|
||||
|
||||
@ -88,7 +92,8 @@ class BoardAreaWidget : public QWidget
|
||||
inline int boardRight() const { return Q_LIKELY(m_boardRight) ? m_boardRight->toInt() + boardMargin: 0; }
|
||||
inline int boardBottom() const { return Q_LIKELY(m_boardBottom) ? m_boardBottom->toInt() + boardMargin: 0; }
|
||||
|
||||
int getTileSize() const { return 25 + m_zoomLevel * 5; }
|
||||
static constexpr int getTileSize(int zoomLevel) { return 25 + zoomLevel * 5; }
|
||||
int getTileSize() const { return getTileSize(m_zoomLevel); }
|
||||
TurnoutPosition getTurnoutPosition(const TileLocation& l) const;
|
||||
BlockState getBlockState(const TileLocation& l) const;
|
||||
std::vector<SensorState> getBlockSensorStates(const TileLocation& l) const;
|
||||
@ -105,10 +110,11 @@ class BoardAreaWidget : public QWidget
|
||||
void paintEvent(QPaintEvent* event) final;
|
||||
|
||||
protected slots:
|
||||
void settingsChanged();
|
||||
void updateMinimumSize();
|
||||
|
||||
public:
|
||||
static constexpr int zoomLevelMin = 0;
|
||||
static constexpr int zoomLevelMin = -2;
|
||||
static constexpr int zoomLevelMax = 15;
|
||||
|
||||
BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr);
|
||||
@ -116,6 +122,7 @@ class BoardAreaWidget : public QWidget
|
||||
Grid grid() const { return m_grid; }
|
||||
void nextGrid();
|
||||
int zoomLevel() const { return m_zoomLevel; }
|
||||
float zoomRatio() const { return static_cast<float>(getTileSize()) / getTileSize(0); }
|
||||
|
||||
void setMouseMoveAction(MouseMoveAction action);
|
||||
void setMouseMoveTileId(TileId id);
|
||||
|
||||
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_90"), TileId::RailCurve90, 0xFF},
|
||||
TileInfo{QStringLiteral(""), TileId::None, 0},
|
||||
TileInfo{QStringLiteral("board_tile.rail.cross_45"), TileId::RailCross45, 0x03},
|
||||
TileInfo{QStringLiteral("board_tile.rail.cross_45"), TileId::RailCross45, 0x0F},
|
||||
TileInfo{QStringLiteral("board_tile.rail.cross_90"), TileId::RailCross90, 0x03},
|
||||
TileInfo{QStringLiteral("board_tile.rail.bridge_45_left"), TileId::RailBridge45Left, 0x0F},
|
||||
TileInfo{QStringLiteral("board_tile.rail.bridge_45_right"), TileId::RailBridge45Right, 0x0F},
|
||||
@ -97,6 +97,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
m_statusBar{new QStatusBar(this)},
|
||||
m_statusBarMessage{new QLabel(this)},
|
||||
m_statusBarCoords{new QLabel(this)},
|
||||
m_statusBarZoom{new QLabel(this)},
|
||||
m_editActions{new QActionGroup(this)},
|
||||
m_editRotate{TileRotate::Deg0}
|
||||
, m_tileMoveStarted{false}
|
||||
@ -107,7 +108,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
connect(name, &AbstractProperty::valueChangedString, this, &BoardWidget::setWindowTitle);
|
||||
setWindowTitle(name->toString());
|
||||
}
|
||||
QMenu* m;
|
||||
QMenu* menu;
|
||||
|
||||
QVBoxLayout* l = new QVBoxLayout();
|
||||
l->setMargin(0);
|
||||
@ -134,23 +135,23 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
m_toolButtonGrid->setToolTip(Locale::tr("qtapp:grid"));
|
||||
m_toolButtonGrid->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
connect(m_toolButtonGrid, &QToolButton::pressed, m_toolButtonGrid, &QToolButton::showMenu);
|
||||
m = new QMenu(this);
|
||||
m_actionGridNone = m->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"),
|
||||
menu = new QMenu(this);
|
||||
m_actionGridNone = menu->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"),
|
||||
[this]()
|
||||
{
|
||||
m_boardArea->setGrid(BoardAreaWidget::Grid::None);
|
||||
});
|
||||
m_actionGridDot = m->addAction(Theme::getIcon("grid_dot"), Locale::tr("qtapp:grid_dot"),
|
||||
m_actionGridDot = menu->addAction(Theme::getIcon("grid_dot"), Locale::tr("qtapp:grid_dot"),
|
||||
[this]()
|
||||
{
|
||||
m_boardArea->setGrid(BoardAreaWidget::Grid::Dot);
|
||||
});
|
||||
m_actionGridLine = m->addAction(Theme::getIcon("grid_line"), Locale::tr("qtapp:grid_line"),
|
||||
m_actionGridLine = menu->addAction(Theme::getIcon("grid_line"), Locale::tr("qtapp:grid_line"),
|
||||
[this]()
|
||||
{
|
||||
m_boardArea->setGrid(BoardAreaWidget::Grid::Line);
|
||||
});
|
||||
m_toolButtonGrid->setMenu(m);
|
||||
m_toolButtonGrid->setMenu(menu);
|
||||
toolbar->addWidget(m_toolButtonGrid);
|
||||
|
||||
// edit toolbar:
|
||||
@ -190,10 +191,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
}));
|
||||
m_editActionDelete->setCheckable(true);
|
||||
m_editActionDelete->setData(-1);
|
||||
if(auto* m = m_object->getMethod("delete_tile"))
|
||||
if(auto* method = m_object->getMethod("delete_tile"))
|
||||
{
|
||||
m_editActionDelete->setEnabled(m->getAttributeBool(AttributeName::Enabled, true));
|
||||
connect(m, &Method::attributeChanged, this,
|
||||
m_editActionDelete->setEnabled(method->getAttributeBool(AttributeName::Enabled, true));
|
||||
connect(method, &Method::attributeChanged, this,
|
||||
[this](AttributeName name, const QVariant& value)
|
||||
{
|
||||
if(name == AttributeName::Enabled)
|
||||
@ -234,10 +235,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
m_addActions.append(action);
|
||||
if(auto* tb = dynamic_cast<QToolButton*>(m_toolbarEdit->widgetForAction(action)))
|
||||
tb->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
QMenu* m = new QMenu(this);
|
||||
QMenu* toolbuttonMenu = new QMenu(this);
|
||||
for(auto subAction : actions)
|
||||
{
|
||||
m->addAction(subAction);
|
||||
toolbuttonMenu->addAction(subAction);
|
||||
connect(subAction, &QAction::triggered, this,
|
||||
[this, action, subAction]()
|
||||
{
|
||||
@ -251,7 +252,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
action->setIcon(actions[0]->icon());
|
||||
action->setText(actions[0]->text());
|
||||
action->setData(actions[0]->data());
|
||||
action->setMenu(m);
|
||||
action->setMenu(toolbuttonMenu);
|
||||
action->setCheckable(true);
|
||||
connect(action, &QAction::triggered, this,
|
||||
[this, action]()
|
||||
@ -269,13 +270,15 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
}
|
||||
}
|
||||
|
||||
if(auto* m = m_object->getMethod("add_tile"))
|
||||
if(auto* method = m_object->getMethod("add_tile"))
|
||||
{
|
||||
const bool v = m->getAttributeBool(AttributeName::Enabled, true);
|
||||
for(QAction* act : m_addActions)
|
||||
act->setEnabled(v);
|
||||
{
|
||||
const bool v = method->getAttributeBool(AttributeName::Enabled, true);
|
||||
for(QAction* act : m_addActions)
|
||||
act->setEnabled(v);
|
||||
}
|
||||
|
||||
connect(m, &Method::attributeChanged, this,
|
||||
connect(method, &Method::attributeChanged, this,
|
||||
[this](AttributeName name, const QVariant& value)
|
||||
{
|
||||
if(name == AttributeName::Enabled)
|
||||
@ -301,10 +304,10 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
if(Q_LIKELY(m_object))
|
||||
m_object->callMethod("resize_to_contents");
|
||||
});
|
||||
if(auto* m = m_object->getMethod("resize_to_contents"))
|
||||
if(auto* method = m_object->getMethod("resize_to_contents"))
|
||||
{
|
||||
m_editActionResizeToContents->setEnabled(m->getAttributeBool(AttributeName::Enabled, true));
|
||||
connect(m, &Method::attributeChanged, this,
|
||||
m_editActionResizeToContents->setEnabled(method->getAttributeBool(AttributeName::Enabled, true));
|
||||
connect(method, &Method::attributeChanged, this,
|
||||
[this](AttributeName name, const QVariant& value)
|
||||
{
|
||||
if(name == AttributeName::Enabled)
|
||||
@ -323,6 +326,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
|
||||
m_statusBar->addWidget(m_statusBarMessage, 1);
|
||||
m_statusBar->addWidget(m_statusBarCoords, 0);
|
||||
m_statusBar->addWidget(m_statusBarZoom, 0);
|
||||
l->addWidget(m_statusBar);
|
||||
|
||||
AbstractProperty* edit = m_object->connection()->world()->getProperty("edit");
|
||||
@ -382,6 +386,7 @@ void BoardWidget::zoomLevelChanged(int value)
|
||||
{
|
||||
m_actionZoomIn->setEnabled(value < BoardAreaWidget::zoomLevelMax);
|
||||
m_actionZoomOut->setEnabled(value > BoardAreaWidget::zoomLevelMin);
|
||||
m_statusBarZoom->setText(QString::number(100 * m_boardArea->zoomRatio(), 'f', 0) + " %");
|
||||
}
|
||||
|
||||
void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
@ -408,18 +413,18 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
m_tileMoveY = y;
|
||||
m_tileMoveStarted = true;
|
||||
|
||||
const auto& data = m_object->tileData().at(l);
|
||||
const auto& tileData = m_object->tileData().at(l);
|
||||
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::MoveTile);
|
||||
m_boardArea->setMouseMoveTileId(data.id());
|
||||
m_boardArea->setMouseMoveTileRotate(data.rotate());
|
||||
m_boardArea->setMouseMoveTileSize(data.width(), data.height());
|
||||
m_boardArea->setMouseMoveTileId(tileData.id());
|
||||
m_boardArea->setMouseMoveTileRotate(tileData.rotate());
|
||||
m_boardArea->setMouseMoveTileSize(tileData.width(), tileData.height());
|
||||
m_boardArea->setMouseMoveHideTileLocation(l);
|
||||
}
|
||||
}
|
||||
else // drop
|
||||
{
|
||||
m_object->moveTile(m_tileMoveX, m_tileMoveY, x, y, false,
|
||||
[this](const bool& r, Message::ErrorCode ec)
|
||||
[this](const bool& /*r*/, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
});
|
||||
m_tileMoveStarted = false;
|
||||
@ -449,10 +454,10 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
m_tileResizeY = l.y;
|
||||
m_tileResizeStarted = true;
|
||||
|
||||
const auto& data = m_object->tileData().at(l);
|
||||
const auto& tileData = m_object->tileData().at(l);
|
||||
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::ResizeTile);
|
||||
m_boardArea->setMouseMoveTileId(data.id());
|
||||
m_boardArea->setMouseMoveTileRotate(data.rotate());
|
||||
m_boardArea->setMouseMoveTileId(tileData.id());
|
||||
m_boardArea->setMouseMoveTileRotate(tileData.rotate());
|
||||
m_boardArea->setMouseMoveHideTileLocation(l);
|
||||
m_boardArea->setMouseMoveTileSizeMax(widthMax, heightMax);
|
||||
}
|
||||
@ -465,7 +470,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
h >= 1 && h <= std::numeric_limits<uint8_t>::max())
|
||||
{
|
||||
m_object->resizeTile(m_tileResizeX, m_tileResizeY, w, h,
|
||||
[this](const bool& r, Message::ErrorCode ec)
|
||||
[this](const bool& /*r*/, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
});
|
||||
|
||||
@ -477,7 +482,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
else if(act == m_editActionDelete)
|
||||
{
|
||||
m_object->deleteTile(x, y,
|
||||
[this](const bool& r, Message::ErrorCode ec)
|
||||
[this](const bool& /*r*/, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
});
|
||||
}
|
||||
@ -487,7 +492,7 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
const Qt::KeyboardModifiers kbMod = QApplication::keyboardModifiers();
|
||||
if(kbMod == Qt::NoModifier || kbMod == Qt::ControlModifier)
|
||||
m_object->addTile(x, y, m_editRotate, classId, kbMod == Qt::ControlModifier,
|
||||
[this](const bool& r, Message::ErrorCode ec)
|
||||
[this](const bool& /*r*/, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
});
|
||||
}
|
||||
@ -517,26 +522,29 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
void BoardWidget::rightClicked()
|
||||
{
|
||||
if(QAction* act = m_editActions->checkedAction())
|
||||
if(int index = act->data().toInt(); index >= 0 && Q_LIKELY(index < tileInfo.size()))
|
||||
if(int index = act->data().toInt(); index >= 0 && Q_LIKELY(static_cast<size_t>(index) < tileInfo.size()))
|
||||
{
|
||||
m_editRotate += TileRotate::Deg45;
|
||||
if(QApplication::keyboardModifiers() == Qt::NoModifier)
|
||||
m_editRotate += TileRotate::Deg45;
|
||||
else if(QApplication::keyboardModifiers() == Qt::ShiftModifier)
|
||||
m_editRotate -= TileRotate::Deg45;
|
||||
validRotate(m_editRotate, tileInfo[index].rotates);
|
||||
m_boardArea->setMouseMoveTileRotate(m_editRotate);
|
||||
}
|
||||
}
|
||||
|
||||
void BoardWidget::actionSelected(const TileInfo* tileInfo)
|
||||
void BoardWidget::actionSelected(const TileInfo* info)
|
||||
{
|
||||
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None);
|
||||
m_tileMoveStarted = false;
|
||||
m_tileResizeStarted = false;
|
||||
|
||||
if(tileInfo)
|
||||
if(info)
|
||||
{
|
||||
validRotate(m_editRotate, tileInfo->rotates);
|
||||
validRotate(m_editRotate, info->rotates);
|
||||
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::AddTile);
|
||||
m_boardArea->setMouseMoveTileRotate(m_editRotate);
|
||||
m_boardArea->setMouseMoveTileId(tileInfo->id);
|
||||
m_boardArea->setMouseMoveTileId(info->id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ class BoardWidget : public QWidget
|
||||
QStatusBar* m_statusBar;
|
||||
QLabel* m_statusBarMessage;
|
||||
QLabel* m_statusBarCoords;
|
||||
QLabel* m_statusBarZoom;
|
||||
QAction* m_actionZoomIn;
|
||||
QAction* m_actionZoomOut;
|
||||
QToolButton* m_toolButtonGrid;
|
||||
|
||||
@ -24,14 +24,20 @@
|
||||
#include <cmath>
|
||||
#include <QtMath>
|
||||
#include <QPainterPath>
|
||||
#include "boardcolorscheme.hpp"
|
||||
#include "../settings/boardsettings.hpp"
|
||||
#include "../utils/rectf.hpp"
|
||||
|
||||
TilePainter::TilePainter(QPainter& painter, int tileSize) :
|
||||
TilePainter::TilePainter(QPainter& painter, int tileSize, const BoardColorScheme& colorScheme) :
|
||||
m_colorScheme{colorScheme},
|
||||
m_turnoutDrawState{BoardSettings::instance().turnoutDrawState},
|
||||
m_trackWidth{tileSize / 5},
|
||||
m_turnoutMargin{tileSize / 10},
|
||||
m_trackPen(trackColor, m_trackWidth, Qt::SolidLine, Qt::FlatCap),
|
||||
m_trackErasePen(backgroundColor, m_trackWidth * 2, Qt::SolidLine, Qt::FlatCap),
|
||||
m_turnoutStatePen(Qt::blue, (m_trackWidth + 1) / 2, Qt::SolidLine, Qt::FlatCap),
|
||||
m_blockPen{m_colorScheme.track},
|
||||
m_trackPen(m_colorScheme.track, m_trackWidth, Qt::SolidLine, Qt::FlatCap),
|
||||
m_trackDisabledPen(m_colorScheme.trackDisabled, m_trackWidth, Qt::SolidLine, Qt::FlatCap),
|
||||
m_trackErasePen(m_colorScheme.background, m_trackWidth * 2, Qt::SolidLine, Qt::FlatCap),
|
||||
m_turnoutStatePen(m_colorScheme.turnoutState, (m_trackWidth + 1) / 2, Qt::SolidLine, Qt::FlatCap),
|
||||
m_painter{painter}
|
||||
{
|
||||
}
|
||||
@ -136,9 +142,9 @@ void TilePainter::draw(TileId id, const QRectF& r, TileRotate rotate)
|
||||
const qreal m = r.width() / 5;
|
||||
const QRectF rArc = r.adjusted(m, m, -m, -m);
|
||||
|
||||
m_painter.setPen(QPen(backgroundColor, m_trackWidth, Qt::SolidLine, Qt::FlatCap));
|
||||
m_painter.setPen(QPen(m_colorScheme.background, m_trackWidth, Qt::SolidLine, Qt::FlatCap));
|
||||
m_painter.drawArc(rArc, angle, angleLength);
|
||||
m_painter.setPen(QPen(trackColor, m_trackWidth / 2., Qt::SolidLine, Qt::FlatCap));
|
||||
m_painter.setPen(QPen(m_colorScheme.track, m_trackWidth / 2., Qt::SolidLine, Qt::FlatCap));
|
||||
m_painter.drawArc(rArc, angle, angleLength);
|
||||
break;
|
||||
}
|
||||
@ -157,7 +163,7 @@ void TilePainter::drawSensor(TileId id, const QRectF& r, TileRotate rotate, Sens
|
||||
setTrackPen();
|
||||
drawStraight(r, rotate);
|
||||
const qreal sz = r.width() / 4;
|
||||
drawLED(r.adjusted(sz, sz, -sz, -sz), sensorStateToColor(state), trackColor);
|
||||
drawLED(r.adjusted(sz, sz, -sz, -sz), sensorStateToColor(state), m_colorScheme.track);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -170,7 +176,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
switch(id)
|
||||
{
|
||||
case TileId::RailTurnoutLeft45:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawCurve45(r, rotate);
|
||||
|
||||
@ -191,7 +197,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutLeft90:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawCurve90(r, rotate);
|
||||
|
||||
@ -212,7 +218,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutLeftCurved:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawCurve45(r, rotate);
|
||||
drawCurve90(r, rotate);
|
||||
|
||||
@ -233,7 +239,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutRight45:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||
|
||||
@ -254,7 +260,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutRight90:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawCurve90(r, rotate + TileRotate::Deg270);
|
||||
|
||||
@ -275,7 +281,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutRightCurved:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||
drawCurve90(r, rotate + TileRotate::Deg270);
|
||||
|
||||
@ -296,7 +302,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutWye:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawCurve45(r, rotate);
|
||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||
|
||||
@ -317,7 +323,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnout3Way:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawCurve45(r, rotate);
|
||||
drawCurve45(r, rotate + TileRotate::Deg225);
|
||||
@ -343,7 +349,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutSingleSlip:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawStraight(r, rotate - TileRotate::Deg45);
|
||||
drawCurve45(r, rotate);
|
||||
@ -366,7 +372,7 @@ void TilePainter::drawTurnout(TileId id, const QRectF& r, TileRotate rotate, Tur
|
||||
break;
|
||||
|
||||
case TileId::RailTurnoutDoubleSlip:
|
||||
setTrackPen();
|
||||
setTurnoutPen();
|
||||
drawStraight(r, rotate);
|
||||
drawStraight(r, rotate - TileRotate::Deg45);
|
||||
drawCurve45(r, rotate);
|
||||
@ -439,19 +445,19 @@ QColor TilePainter::sensorStateToColor(SensorState value) const
|
||||
switch(value)
|
||||
{
|
||||
case SensorState::Occupied:
|
||||
return sensorColorOccupied;
|
||||
return m_colorScheme.sensorOccupied;
|
||||
|
||||
case SensorState::Free:
|
||||
return sensorColorFree;
|
||||
return m_colorScheme.sensorFree;
|
||||
|
||||
case SensorState::Idle:
|
||||
return sensorColorIdle;
|
||||
return m_colorScheme.sensorIdle;
|
||||
|
||||
case SensorState::Triggered:
|
||||
return sensorColorTriggered;
|
||||
return m_colorScheme.sensorTriggered;
|
||||
|
||||
case SensorState::Unknown:
|
||||
return sensorColorUnknown;
|
||||
return m_colorScheme.sensorUnknown;
|
||||
}
|
||||
assert(false);
|
||||
return QColor();
|
||||
@ -462,15 +468,15 @@ void TilePainter::setBlockStateBrush(BlockState value)
|
||||
switch(value)
|
||||
{
|
||||
case BlockState::Occupied:
|
||||
m_painter.setBrush(blockBrushOccupied);
|
||||
m_painter.setBrush(m_colorScheme.blockOccupied);
|
||||
break;
|
||||
|
||||
case BlockState::Free:
|
||||
m_painter.setBrush(blockBrushFree);
|
||||
m_painter.setBrush(m_colorScheme.blockFree);
|
||||
break;
|
||||
|
||||
case BlockState::Unknown:
|
||||
m_painter.setBrush(blockBrushUnknown);
|
||||
m_painter.setBrush(m_colorScheme.blockUnknown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -768,7 +774,7 @@ void TilePainter::drawRailBlock(const QRectF& r, TileRotate rotate, BlockState s
|
||||
{
|
||||
m_painter.drawLine(topCenter(r), bottomCenter(r));
|
||||
setBlockStateBrush(state);
|
||||
m_painter.setPen(blockPen);
|
||||
m_painter.setPen(m_blockPen);
|
||||
const qreal m = 0.5 + qFloor(r.width() / 10);
|
||||
const QRectF block = r.adjusted(m, m, -m, -m);
|
||||
m_painter.drawRect(block);
|
||||
@ -790,7 +796,7 @@ void TilePainter::drawRailBlock(const QRectF& r, TileRotate rotate, BlockState s
|
||||
{
|
||||
m_painter.drawLine(centerLeft(r), centerRight(r));
|
||||
setBlockStateBrush(state);
|
||||
m_painter.setPen(blockPen);
|
||||
m_painter.setPen(m_blockPen);
|
||||
const qreal m = 0.5 + qFloor(r.height() / 10);
|
||||
const QRectF block = r.adjusted(m, m, -m, -m);
|
||||
m_painter.drawRect(block);
|
||||
|
||||
@ -34,38 +34,34 @@
|
||||
#include <traintastic/enum/tristate.hpp>
|
||||
#include <traintastic/enum/turnoutposition.hpp>
|
||||
|
||||
struct BoardColorScheme;
|
||||
|
||||
class TilePainter
|
||||
{
|
||||
public:
|
||||
inline static const QColor backgroundColor{0x10, 0x10, 0x10};
|
||||
|
||||
private:
|
||||
inline static const QColor trackColor{0xC0, 0xC0, 0xC0};
|
||||
inline static const QColor signalRed{192, 0, 0};
|
||||
inline static const QColor signalYellow{192, 192, 32};
|
||||
inline static const QColor signalGreen{0, 192, 0};
|
||||
inline static const QBrush blockBrushFree{QColor{0x66, 0xC6, 0x66}};
|
||||
inline static const QBrush blockBrushOccupied{QColor{0xC6, 0x66, 0x66}};
|
||||
inline static const QBrush blockBrushUnknown{QColor{0x66, 0x66, 0x66}};
|
||||
inline static const QColor sensorColorFree{0x66, 0xC6, 0x66};
|
||||
inline static const QColor sensorColorOccupied{0xC6, 0x66, 0x66};
|
||||
inline static const QColor sensorColorIdle{0x40, 0x40, 0x40};
|
||||
inline static const QColor sensorColorTriggered{0x00, 0xBF, 0xFF};
|
||||
inline static const QColor sensorColorUnknown{0x10, 0x10, 0x10};
|
||||
inline static const QPen blockPen{trackColor};
|
||||
|
||||
const BoardColorScheme& m_colorScheme;
|
||||
const bool m_turnoutDrawState;
|
||||
const int m_trackWidth;
|
||||
const int m_turnoutMargin;
|
||||
const QPen m_blockPen;
|
||||
const QPen m_trackPen;
|
||||
const QPen m_trackDisabledPen;
|
||||
const QPen m_trackErasePen;
|
||||
const QPen m_turnoutPen;
|
||||
const QPen m_turnoutStatePen;
|
||||
|
||||
QPainter& m_painter;
|
||||
|
||||
inline void setTrackPen() { m_painter.setPen(m_trackPen); }
|
||||
inline void setTrackDisabledPen() { m_painter.setPen(m_trackDisabledPen); }
|
||||
inline void setTrackErasePen() { m_painter.setPen(m_trackErasePen); }
|
||||
inline void setTurnoutStatePen() { m_painter.setPen(m_turnoutStatePen); }
|
||||
inline QRectF turnoutStateRect(const QRectF& r) { return r.adjusted(m_turnoutMargin, m_turnoutMargin, -m_turnoutMargin, -m_turnoutMargin); }
|
||||
inline void setTurnoutPen() { m_painter.setPen(m_turnoutDrawState ? m_trackPen : m_trackDisabledPen); }
|
||||
inline void setTurnoutStatePen() { m_painter.setPen(m_turnoutDrawState ? m_turnoutStatePen : m_trackPen); }
|
||||
inline QRectF turnoutStateRect(const QRectF& r) { return m_turnoutDrawState ? r.adjusted(m_turnoutMargin, m_turnoutMargin, -m_turnoutMargin, -m_turnoutMargin) : r; }
|
||||
|
||||
QColor sensorStateToColor(SensorState value) const;
|
||||
void setBlockStateBrush(BlockState value);
|
||||
@ -84,7 +80,7 @@ class TilePainter
|
||||
void drawRailBlock(const QRectF& r, TileRotate rotate, BlockState state = BlockState::Unknown, const std::vector<SensorState> subStates = {});
|
||||
|
||||
public:
|
||||
TilePainter(QPainter& painter, int tileSize);
|
||||
TilePainter(QPainter& painter, int tileSize, const BoardColorScheme& colorScheme);
|
||||
|
||||
void draw(TileId id, const QRectF& r, TileRotate rotate);
|
||||
void drawSensor(TileId id, const QRectF& r, TileRotate rotate, SensorState state = SensorState::Unknown);
|
||||
|
||||
@ -82,7 +82,7 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par
|
||||
m_object = object;
|
||||
|
||||
m_requestId = m_item.object().connection()->getTableModel(m_object,
|
||||
[this, spinner](const TableModelPtr& tableModel, Message::ErrorCode ec)
|
||||
[this, spinner](const TableModelPtr& tableModel, Message::ErrorCode errorCode)
|
||||
{
|
||||
if(tableModel)
|
||||
{
|
||||
@ -104,7 +104,7 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par
|
||||
delete spinner;
|
||||
}
|
||||
else
|
||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
|
||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode)));
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@ -72,7 +72,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
|
||||
m_object = object;
|
||||
|
||||
m_requestId = m_connection->getTableModel(m_object,
|
||||
[this, spinner](const TableModelPtr& tableModel, Message::ErrorCode ec)
|
||||
[this, spinner](const TableModelPtr& tableModel, Message::ErrorCode errorCode)
|
||||
{
|
||||
if(tableModel)
|
||||
{
|
||||
@ -95,7 +95,7 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
|
||||
delete spinner;
|
||||
}
|
||||
else
|
||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
|
||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(errorCode)));
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@ -46,7 +46,7 @@ void MdiArea::addBackgroundAction(QAction* action)
|
||||
|
||||
void MdiArea::removeBackgroundAction(QAction* action)
|
||||
{
|
||||
auto it = std::find_if(m_backgroundActionButtons.begin(), m_backgroundActionButtons.end(), [action](const auto& it) { return it.first == action; });
|
||||
auto it = std::find_if(m_backgroundActionButtons.begin(), m_backgroundActionButtons.end(), [action](const auto& i) { return i.first == action; });
|
||||
if(it != m_backgroundActionButtons.end())
|
||||
{
|
||||
delete it->second;
|
||||
|
||||
@ -72,10 +72,10 @@ class AbstractProperty : public BaseProperty
|
||||
return setValueInt64(static_cast<int64_t>(value));
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual int setValueBool(bool value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueInt64(int64_t value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueDouble(double value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueString(const QString& value, std::function<void(const QString& error)> callback) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueBool(bool value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueInt64(int64_t value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueDouble(double value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
|
||||
[[nodiscard]] virtual int setValueString(const QString& value, std::function<void(const QString& error)> /*callback*/) { Q_ASSERT(value != value); return -1; }
|
||||
|
||||
signals:
|
||||
void valueChanged();
|
||||
|
||||
@ -39,12 +39,12 @@ class AbstractVectorProperty : public BaseProperty
|
||||
public:
|
||||
virtual int size() const = 0;
|
||||
|
||||
virtual bool getBool(int index) const { Q_ASSERT(false); return false; }
|
||||
virtual int getInt(int index) const { Q_ASSERT(false); return 0; }
|
||||
virtual int64_t getInt64(int index) const { Q_ASSERT(false); return 0; }
|
||||
virtual double getDouble(int index) const { Q_ASSERT(false); return 0; }
|
||||
virtual QString getString(int index) const { Q_ASSERT(false); return ""; }
|
||||
virtual QVariant getVariant(int index) const { Q_ASSERT(false); return QVariant(); }
|
||||
virtual bool getBool(int /*index*/) const { Q_ASSERT(false); return false; }
|
||||
virtual int getInt(int /*index*/) const { Q_ASSERT(false); return 0; }
|
||||
virtual int64_t getInt64(int /*index*/) const { Q_ASSERT(false); return 0; }
|
||||
virtual double getDouble(int /*index*/) const { Q_ASSERT(false); return 0; }
|
||||
virtual QString getString(int /*index*/) const { Q_ASSERT(false); return ""; }
|
||||
virtual QVariant getVariant(int /*index*/) const { Q_ASSERT(false); return QVariant(); }
|
||||
|
||||
template<typename T>
|
||||
T getEnum(int index) const
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
#include "connection.hpp"
|
||||
#include "callmethod.hpp"
|
||||
|
||||
Board::Board(std::shared_ptr<Connection> connection, Handle handle, const QString& classId) :
|
||||
Board::Board(std::shared_ptr<Connection> connection, Handle handle) :
|
||||
Object(std::move(connection), handle, classId),
|
||||
m_getTileDataRequestId{Connection::invalidRequestId}
|
||||
{
|
||||
|
||||
@ -56,7 +56,7 @@ class Board final : public Object
|
||||
public:
|
||||
inline static const QString classId = QStringLiteral("board");
|
||||
|
||||
Board(std::shared_ptr<Connection> connection, Handle handle, const QString& classId);
|
||||
Board(std::shared_ptr<Connection> connection, Handle handle);
|
||||
~Board() final;
|
||||
|
||||
void getTileData();
|
||||
|
||||
@ -483,7 +483,7 @@ ObjectPtr Connection::readObject(const Message& message)
|
||||
else if(classId.startsWith(OutputMap::classIdPrefix))
|
||||
p = new OutputMap(shared_from_this(), handle, classId);
|
||||
else if(classId == Board::classId)
|
||||
p = new Board(shared_from_this(), handle, classId);
|
||||
p = new Board(shared_from_this(), handle);
|
||||
else
|
||||
p = new Object(shared_from_this(), handle, classId);
|
||||
|
||||
@ -593,14 +593,14 @@ ObjectPtr Connection::readObject(const Message& message)
|
||||
{
|
||||
message.readBlock(); // item
|
||||
const AttributeName attributeName = message.read<AttributeName>();
|
||||
const ValueType type = message.read<ValueType>();
|
||||
const ValueType valueType = message.read<ValueType>();
|
||||
|
||||
switch(message.read<AttributeType>())
|
||||
{
|
||||
case AttributeType::Value:
|
||||
{
|
||||
QVariant value;
|
||||
switch(type)
|
||||
switch(valueType)
|
||||
{
|
||||
case ValueType::Boolean:
|
||||
value = message.read<bool>();
|
||||
@ -632,7 +632,7 @@ ObjectPtr Connection::readObject(const Message& message)
|
||||
case AttributeType::Values:
|
||||
{
|
||||
const int length = message.read<int>(); // read uint32_t as int, Qt uses int for length
|
||||
QList<QVariant> values = readArray(message, type, length);
|
||||
QList<QVariant> values = readArray(message, valueType, length);
|
||||
if(Q_LIKELY(values.length() == length))
|
||||
item->m_attributes[attributeName] = values;
|
||||
break;
|
||||
@ -683,7 +683,7 @@ void Connection::getWorld()
|
||||
setWorld(nullptr);
|
||||
else
|
||||
m_worldRequestId = getObject(m_worldProperty->objectId(),
|
||||
[this](const ObjectPtr& object, Message::ErrorCode ec)
|
||||
[this](const ObjectPtr& object, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
m_worldRequestId = invalidRequestId;
|
||||
setWorld(object);
|
||||
@ -1039,22 +1039,22 @@ void Connection::processMessage(const std::shared_ptr<Message> message)
|
||||
|
||||
void Connection::socketConnected()
|
||||
{
|
||||
std::unique_ptr<Message> request{Message::newRequest(Message::Command::Login)};
|
||||
request->write(m_username.toUtf8());
|
||||
request->write(m_password);
|
||||
send(request,
|
||||
[this](const std::shared_ptr<Message> message)
|
||||
std::unique_ptr<Message> loginRequest{Message::newRequest(Message::Command::Login)};
|
||||
loginRequest->write(m_username.toUtf8());
|
||||
loginRequest->write(m_password);
|
||||
send(loginRequest,
|
||||
[this](const std::shared_ptr<Message> loginResponse)
|
||||
{
|
||||
if(message && message->isResponse() && !message->isError())
|
||||
if(loginResponse && loginResponse->isResponse() && !loginResponse->isError())
|
||||
{
|
||||
std::unique_ptr<Message> request{Message::newRequest(Message::Command::NewSession)};
|
||||
send(request,
|
||||
[this](const std::shared_ptr<Message> message)
|
||||
std::unique_ptr<Message> newSessionRequest{Message::newRequest(Message::Command::NewSession)};
|
||||
send(newSessionRequest,
|
||||
[this](const std::shared_ptr<Message> newSessionResonse)
|
||||
{
|
||||
if(message && message->isResponse() && !message->isError())
|
||||
if(newSessionResonse && newSessionResonse->isResponse() && !newSessionResonse->isError())
|
||||
{
|
||||
message->read(m_sessionUUID);
|
||||
m_traintastic = readObject(*message);
|
||||
newSessionResonse->read(m_sessionUUID);
|
||||
m_traintastic = readObject(*newSessionResonse);
|
||||
m_worldProperty = dynamic_cast<ObjectProperty*>(m_traintastic->getProperty("world"));
|
||||
connect(m_worldProperty, &ObjectProperty::valueChanged, this,
|
||||
[this]()
|
||||
|
||||
@ -51,11 +51,11 @@ void OutputMap::getItems()
|
||||
{
|
||||
m_getItemsRequestId = Connection::invalidRequestId;
|
||||
|
||||
if(auto c = connection())
|
||||
if(auto con = connection())
|
||||
{
|
||||
Items objects;
|
||||
while(!response->endOfMessage())
|
||||
objects.emplace_back(c->readObject(*response));
|
||||
objects.emplace_back(con->readObject(*response));
|
||||
m_items = std::move(objects);
|
||||
|
||||
emit itemsChanged();
|
||||
|
||||
@ -122,7 +122,7 @@ void ServerLogTableModel::processMessage(const Message& message)
|
||||
log.code = message.read<LogMessage>();
|
||||
log.message = Locale::tr("message:" + toCodeString(log.code));
|
||||
const int argc = message.read<uint8_t>();
|
||||
for(int i = 0; i < argc; i++)
|
||||
for(int j = 0; j < argc; j++)
|
||||
log.message = log.message.arg(QString::fromUtf8(message.read<QByteArray>()));
|
||||
m_logs.append(log);
|
||||
}
|
||||
|
||||
@ -31,10 +31,43 @@ QString errorCodeToText(Message::ErrorCode ec)
|
||||
QString text;
|
||||
switch(ec)
|
||||
{
|
||||
case ErrorCode::InvalidCommand:
|
||||
text = "Invalid command";
|
||||
break;
|
||||
|
||||
case ErrorCode::Failed:
|
||||
text = "Failed";
|
||||
break;
|
||||
|
||||
case ErrorCode::AuthenticationFailed:
|
||||
text = "Authentication failed";
|
||||
break;
|
||||
|
||||
case ErrorCode::InvalidSession:
|
||||
text = "Invalid session";
|
||||
break;
|
||||
|
||||
case ErrorCode::UnknownObject:
|
||||
text = "Unknown object";
|
||||
break;
|
||||
|
||||
case ErrorCode::ObjectNotTable:
|
||||
text = "Object not table";
|
||||
break;
|
||||
|
||||
case ErrorCode::UnknownClassId:
|
||||
text = "Unknown class id";
|
||||
break;
|
||||
|
||||
case ErrorCode::Other:
|
||||
assert(false);
|
||||
text = "Other";
|
||||
break;
|
||||
|
||||
case ErrorCode::Unknown:
|
||||
text = "Unknown";
|
||||
break;
|
||||
|
||||
case ErrorCode::None:
|
||||
break; // silence warning
|
||||
}
|
||||
|
||||
@ -23,14 +23,25 @@
|
||||
#ifndef TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
|
||||
#define TRAINTASTIC_CLIENT_SETTINGS_BOARDSETTINGS_HPP
|
||||
|
||||
#include <array>
|
||||
#include "settingsbase.hpp"
|
||||
#include "setting.hpp"
|
||||
#include <traintastic/enum/enum.hpp>
|
||||
|
||||
class BoardSettings : public SettingsBase
|
||||
{
|
||||
public:
|
||||
enum class ColorScheme
|
||||
{
|
||||
Dark = 0,
|
||||
Light = 1,
|
||||
};
|
||||
|
||||
private:
|
||||
BoardSettings()
|
||||
: SettingsBase("board")
|
||||
, colorScheme{*this, "color_scheme", ColorScheme::Dark}
|
||||
, turnoutDrawState{*this, "turnout_draw_state", true}
|
||||
, showBlockSensorStates{*this, "show_block_sensor_states", true}
|
||||
{
|
||||
}
|
||||
@ -42,7 +53,23 @@ class BoardSettings : public SettingsBase
|
||||
return settings;
|
||||
}
|
||||
|
||||
Setting<ColorScheme> colorScheme;
|
||||
Setting<bool> turnoutDrawState;
|
||||
Setting<bool> showBlockSensorStates;
|
||||
};
|
||||
|
||||
ENUM_NAME(BoardSettings::ColorScheme, "board_settings.color_scheme")
|
||||
|
||||
ENUM_VALUES(BoardSettings::ColorScheme, 2,
|
||||
{
|
||||
{BoardSettings::ColorScheme::Dark, "dark"},
|
||||
{BoardSettings::ColorScheme::Light, "light"},
|
||||
})
|
||||
|
||||
template<>
|
||||
struct SettingEnum<BoardSettings::ColorScheme>
|
||||
{
|
||||
static constexpr std::array<BoardSettings::ColorScheme, 2> values = {BoardSettings::ColorScheme::Dark, BoardSettings::ColorScheme::Light};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -28,6 +28,8 @@ BoardSettingsWidget::BoardSettingsWidget(QWidget* parent)
|
||||
{
|
||||
BoardSettings& s = BoardSettings::instance();
|
||||
|
||||
addSetting(s.colorScheme);
|
||||
addSetting(s.turnoutDrawState);
|
||||
addSetting(s.showBlockSensorStates);
|
||||
|
||||
done();
|
||||
|
||||
@ -66,9 +66,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(QWidget* parent)
|
||||
{
|
||||
GeneralSettings::instance().language.setValue(cb->itemData(index).toString());
|
||||
});
|
||||
add(GeneralSettings::instance().language.name(), cb);
|
||||
add(s.language.name(), cb);
|
||||
}
|
||||
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
@ -25,6 +25,12 @@
|
||||
|
||||
#include "settingsbase.hpp"
|
||||
|
||||
template<class T>
|
||||
struct SettingEnum
|
||||
{
|
||||
static_assert(sizeof(T) != sizeof(T));
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class Setting
|
||||
{
|
||||
|
||||
@ -39,16 +39,30 @@ class SettingsBase : public QObject
|
||||
template<class T>
|
||||
inline T get(const QString& key, const T& defaultValue) const
|
||||
{
|
||||
return qvariant_cast<T>(m_settings.value(key, defaultValue));
|
||||
if constexpr(std::is_enum_v<T>)
|
||||
return static_cast<T>(m_settings.value(key, static_cast<uint>(defaultValue)).toUInt());
|
||||
else
|
||||
return qvariant_cast<T>(m_settings.value(key, defaultValue));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void set(const QString& key, const T& value)
|
||||
{
|
||||
if(m_settings.value(key) != value)
|
||||
if constexpr(std::is_enum_v<T>)
|
||||
{
|
||||
m_settings.setValue(key, value);
|
||||
emit changed();
|
||||
if(m_settings.value(key) != static_cast<uint>(value))
|
||||
{
|
||||
m_settings.setValue(key, static_cast<uint>(value));
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_settings.value(key) != value)
|
||||
{
|
||||
m_settings.setValue(key, value);
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,10 @@
|
||||
#define TRAINTASTIC_CLIENT_SETTINGS_SETTINGSBASEWIDGET_HPP
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <QComboBox>
|
||||
#include <traintastic/enum/enum.hpp>
|
||||
#include "setting.hpp"
|
||||
#include "../utils/enum.hpp"
|
||||
|
||||
class SettingsBaseWidget : public QScrollArea
|
||||
{
|
||||
@ -40,11 +43,31 @@ class SettingsBaseWidget : public QScrollArea
|
||||
void addSettingOnOff(Setting<bool>& setting);
|
||||
void addSettingDir(Setting<QString>& setting);
|
||||
|
||||
template<class T>
|
||||
void addSettingEnumDropdown(Setting<T>& setting)
|
||||
{
|
||||
QComboBox* cb = new QComboBox(widget());
|
||||
for(auto value : SettingEnum<T>::values)
|
||||
{
|
||||
cb->addItem(translateEnum(EnumName<T>::value, static_cast<quint64>(value)), static_cast<uint>(value));
|
||||
if(setting.value() == value)
|
||||
cb->setCurrentIndex(cb->count() - 1);
|
||||
}
|
||||
connect(cb, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[&setting, cb](int /*index*/)
|
||||
{
|
||||
setting.setValue(static_cast<T>(cb->currentData().toUInt()));
|
||||
});
|
||||
add(setting.name(), cb);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void addSetting(Setting<T>& setting)
|
||||
{
|
||||
if constexpr(std::is_same_v<T, bool>)
|
||||
addSettingOnOff(setting);
|
||||
else if constexpr(std::is_enum_v<T>)
|
||||
addSettingEnumDropdown(setting);
|
||||
else
|
||||
static_assert(sizeof(T) != sizeof(T));
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -42,6 +42,7 @@
|
||||
#include <traintastic/enum/xpressnetcommandstation.hpp>
|
||||
#include <traintastic/enum/xpressnetinterfacetype.hpp>
|
||||
#include <traintastic/enum/xpressnetserialinterfacetype.hpp>
|
||||
#include "../settings/boardsettings.hpp"
|
||||
|
||||
#define GET_ENUM_VALUES(_type) \
|
||||
if(enumName == EnumName<_type>::value) \
|
||||
@ -87,6 +88,7 @@ QString translateEnum(const QString& enumName, qint64 value)
|
||||
TRANSLATE_ENUM(XpressNetCommandStation)
|
||||
TRANSLATE_ENUM(XpressNetInterfaceType)
|
||||
TRANSLATE_ENUM(XpressNetSerialInterfaceType)
|
||||
TRANSLATE_ENUM(BoardSettings::ColorScheme)
|
||||
return enumName + "@" + QString::number(value);
|
||||
}
|
||||
|
||||
|
||||
@ -82,7 +82,6 @@ void ItemsEditWidget::buildForm()
|
||||
QVBoxLayout* left = new QVBoxLayout();
|
||||
|
||||
QToolBar* toolbar = new QToolBar(this);
|
||||
QAction* act;
|
||||
|
||||
if(m_methodAdd)
|
||||
toolbar->addAction(new MethodAction(Theme::getIcon("add"), *m_methodAdd, toolbar));
|
||||
@ -133,7 +132,7 @@ void ItemsEditWidget::buildForm()
|
||||
connect(m_propertyItems, &ObjectVectorProperty::valueChanged, this, &ItemsEditWidget::itemsChanged);
|
||||
itemsChanged();
|
||||
connect(m_list, &QListWidget::currentItemChanged, this,
|
||||
[this](QListWidgetItem* current, QListWidgetItem* previous)
|
||||
[this](QListWidgetItem* current, QListWidgetItem* /*previous*/)
|
||||
{
|
||||
if(current)
|
||||
if(auto* w = m_items.value(current->data(objectIdRole).toString()))
|
||||
|
||||
@ -96,7 +96,7 @@ void ObjectEditWidget::buildForm()
|
||||
ObjectProperty* property = static_cast<ObjectProperty*>(baseProperty);
|
||||
if(contains(baseProperty->flags(), PropertyFlags::SubObject))
|
||||
{
|
||||
QWidget* w = new ObjectEditWidget(property->objectId());
|
||||
w = new ObjectEditWidget(property->objectId());
|
||||
w->setWindowTitle(property->displayName());
|
||||
tabs.append(w);
|
||||
continue;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -51,9 +51,9 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_requestIdInputMonitor{Connection::invalidRequestId},
|
||||
m_requestIdOutputKeyboard{Connection::invalidRequestId},
|
||||
m_buttonAdd{nullptr},
|
||||
m_object{object},
|
||||
m_toolbar{new QToolBar()},
|
||||
m_buttonAdd{nullptr},
|
||||
m_actionAdd{nullptr},
|
||||
m_actionEdit{nullptr},
|
||||
m_actionDelete{nullptr},
|
||||
@ -106,12 +106,12 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
|
||||
m_object->connection()->cancelRequest(m_requestIdAdd);
|
||||
|
||||
m_requestIdAdd = method->call(
|
||||
[this](const ObjectPtr& object, Message::ErrorCode /*ec*/)
|
||||
[this](const ObjectPtr& addedObject, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
m_requestIdAdd = Connection::invalidRequestId;
|
||||
if(object)
|
||||
if(addedObject)
|
||||
{
|
||||
MainWindow::instance->showObject(object);
|
||||
MainWindow::instance->showObject(addedObject);
|
||||
}
|
||||
// TODO: show error
|
||||
});
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "propertycombobox.hpp"
|
||||
#include <cassert>
|
||||
#include "../network/property.hpp"
|
||||
#include "../utils/internalupdateholder.hpp"
|
||||
#include "../utils/enum.hpp"
|
||||
@ -103,6 +104,15 @@ void PropertyComboBox::updateValues()
|
||||
setCurrentIndex(count() - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case ValueType::Invalid:
|
||||
case ValueType::Boolean:
|
||||
case ValueType::Float:
|
||||
case ValueType::String:
|
||||
case ValueType::Object:
|
||||
case ValueType::Set:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +182,10 @@ PropertyLuaCodeEdit::Highlighter::Highlighter(QTextDocument* parent) :
|
||||
m_rules.append(Rule(regex, Qt::magenta));
|
||||
|
||||
const auto globals = {
|
||||
QStringLiteral("\\log(?=\\.)"),
|
||||
QStringLiteral("\\bmath(?=\\.)"),
|
||||
QStringLiteral("\\bstring(?=\\.)"),
|
||||
QStringLiteral("\\btable(?=\\.)"),
|
||||
QStringLiteral("\\blog(?=\\.)"),
|
||||
QStringLiteral("\\bworld(?=\\.)"),
|
||||
QStringLiteral("\\bset(?=\\.)"),
|
||||
QStringLiteral("\\benum(?=\\.)"),
|
||||
|
||||
@ -91,7 +91,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
||||
});
|
||||
}
|
||||
|
||||
if(m_emergencyStop = m_object->getProperty("emergency_stop"))
|
||||
if((m_emergencyStop = m_object->getProperty("emergency_stop")))
|
||||
{
|
||||
m_speedoMeter->setEStop(m_emergencyStop->toBool());
|
||||
connect(m_emergencyStop, &AbstractProperty::valueChangedBool, m_speedoMeter, &SpeedoMeterWidget::setEStop);
|
||||
@ -100,7 +100,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
||||
if(auto* p = dynamic_cast<ObjectProperty*>(m_object->getProperty("functions")))
|
||||
{
|
||||
m_functionsRequestId = m_object->connection()->getObject(p->objectId(),
|
||||
[this](const ObjectPtr& functions, Message::ErrorCode ec)
|
||||
[this](const ObjectPtr& functions, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
m_functionsRequestId = Connection::invalidRequestId;
|
||||
|
||||
@ -113,7 +113,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
||||
for(const QString& id : *items)
|
||||
{
|
||||
const int functionRequestId = functions->connection()->getObject(id,
|
||||
[this, i](const ObjectPtr& function, Message::ErrorCode ec)
|
||||
[this, i](const ObjectPtr& function, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
m_functionRequestIds.erase(i);
|
||||
|
||||
@ -263,9 +263,7 @@ ThrottleFunctionButton* ThrottleWidget::getFunctionButton(DecoderFunctionFunctio
|
||||
auto it = std::find_if(m_functionButtons.begin(), m_functionButtons.end(),
|
||||
[function](const auto* btn)
|
||||
{
|
||||
auto n = btn->number();
|
||||
auto f = btn->function();
|
||||
return f == function;
|
||||
return btn->function() == function;
|
||||
});
|
||||
return (it != m_functionButtons.end()) ? *it : nullptr;
|
||||
}
|
||||
|
||||
@ -169,6 +169,18 @@
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/** Lua **********************************************************************/
|
||||
|
||||
code.lua span.const
|
||||
{
|
||||
color: #4e004e;
|
||||
}
|
||||
|
||||
code.lua span.keyword
|
||||
{
|
||||
color: #000080;
|
||||
}
|
||||
|
||||
/** MEDIA BREAKPOINTS ********************************************************/
|
||||
|
||||
@media screen and (max-width: 991px)
|
||||
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
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",
|
||||
"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",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import codecs
|
||||
import pycmarkgfm # pip3 install pycmarkgfm
|
||||
import cmarkgfm # pip3 install cmarkgfm
|
||||
from .builder import Builder
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ class HTMLBuilder(Builder):
|
||||
|
||||
def _file_to_html(self, page):
|
||||
with codecs.open(os.path.join(self._language_dir, page['markdown']), 'r', 'utf-8') as md:
|
||||
html = pycmarkgfm.gfm_to_html(md.read())
|
||||
html = cmarkgfm.github_flavored_markdown_to_html(md.read())
|
||||
|
||||
# parse id
|
||||
html = re.sub(r'<h([1-6])([^>]*)>(.*) {#([a-z0-9-]+)}</h\1>', r'<h\1\2 id="\4">\3</h\1>', html)
|
||||
@ -21,6 +21,9 @@ class HTMLBuilder(Builder):
|
||||
# set target="_blank" for external links:
|
||||
html = re.sub(r'<a([^>]+href="http(s|)://)', r'<a target="_blank"\1', html)
|
||||
|
||||
if 'code' in page and page['code'] == 'lua':
|
||||
html = re.sub(r'<code>(.+)</code>', self._highlight_lua, html)
|
||||
|
||||
# change img title attribute to figcaption
|
||||
html = re.sub(r'(<img[^>]+)title="([^">]*)"([^>]*>)',
|
||||
lambda m:
|
||||
@ -30,3 +33,9 @@ class HTMLBuilder(Builder):
|
||||
html)
|
||||
|
||||
return html
|
||||
|
||||
def _highlight_lua(self, m):
|
||||
code = m.group(1)
|
||||
code = re.sub(r'\b([A-Z_][A-Z0-9_]*)\b', r'<span class="const">\1</span>', code) # CONSTANTS
|
||||
code = re.sub(r'\b(and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b', r'<span class="keyword">\1</span>', code) # keywords
|
||||
return '<code class="lua">' + code + '</code>'
|
||||
|
||||
@ -36,6 +36,15 @@ class HTMLSinglePageBuilder(HTMLBuilder):
|
||||
|
||||
return html
|
||||
|
||||
def subpages(self, page, depth=1):
|
||||
html = ''
|
||||
if 'pages' in page:
|
||||
for subpage in page['pages']:
|
||||
subhtml = self._file_to_html(subpage)
|
||||
subhtml = re.sub(r'<h([1-5])([^>]*)>(.*?)</h\1>', lambda m: '<h' + str(min(6, int(m.group(1)) + depth)) + m.group(2) + '>' + m.group(3) + '</h' + str(min(6, int(m.group(1))) + depth) + '>', subhtml)
|
||||
html += subhtml + self.subpages(subpage, depth + 1)
|
||||
return html
|
||||
|
||||
def build(self):
|
||||
self._output_copy_files([
|
||||
'css/pure-min.css',
|
||||
@ -46,12 +55,7 @@ class HTMLSinglePageBuilder(HTMLBuilder):
|
||||
toc = {'preface': [], 'chapter': [], 'appendix': []}
|
||||
manual_html = ''
|
||||
for page in self._json:
|
||||
page_html = self._file_to_html(page)
|
||||
if 'pages' in page:
|
||||
for subpage in page['pages']:
|
||||
subhtml = self._file_to_html(subpage)
|
||||
subhtml = re.sub(r'<h([1-5])([^>]*)>(.*?)</h\1>', lambda m: '<h' + str(int(m.group(1)) + 1) + m.group(2) + '>' + m.group(3) + '</h' + str(int(m.group(1)) + 1) + '>', subhtml)
|
||||
page_html += subhtml
|
||||
page_html = self._file_to_html(page) + self.subpages(page)
|
||||
|
||||
m = re.findall(r'<h([1-2])([^>]*)>(.*?)</h\1>', page_html)
|
||||
if m is not None:
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
Board::Board(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
IdObject(world, _id),
|
||||
name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
|
||||
left{this, "left", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
||||
top{this, "top", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
||||
right{this, "right", 0, PropertyFlags::ReadOnly | PropertyFlags::Store},
|
||||
@ -46,23 +46,36 @@ Board::Board(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
|
||||
if(auto it = m_tiles.find(l); it != m_tiles.end())
|
||||
{
|
||||
if(!replace && tileClassId == StraightRailTile::classId && it->second->tileId() == TileId::RailStraight) // merge to bridge
|
||||
if(!replace)
|
||||
{
|
||||
const TileRotate tileRotate = it->second->rotate;
|
||||
if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y))
|
||||
|
||||
if(it->second->tileId() == TileId::RailStraight && tileClassId == StraightRailTile::classId) // merge to bridge
|
||||
{
|
||||
tileClassId = Bridge90RailTile::classId;
|
||||
rotate = tileRotate;
|
||||
if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y))
|
||||
{
|
||||
tileClassId = Bridge90RailTile::classId;
|
||||
rotate = tileRotate;
|
||||
}
|
||||
else if((tileRotate == rotate + TileRotate::Deg45 || tileRotate == rotate + TileRotate::Deg225) && deleteTile(x, y))
|
||||
{
|
||||
tileClassId = Bridge45LeftRailTile::classId;
|
||||
rotate = tileRotate;
|
||||
}
|
||||
else if((tileRotate == rotate - TileRotate::Deg45 || tileRotate == rotate - TileRotate::Deg225) && deleteTile(x, y))
|
||||
{
|
||||
tileClassId = Bridge45RightRailTile::classId;
|
||||
rotate = tileRotate;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if((tileRotate == rotate + TileRotate::Deg45 || tileRotate == rotate + TileRotate::Deg225) && deleteTile(x, y))
|
||||
else if(it->second->tileId() == TileId::RailStraight && // replace straight by a straight with something extra
|
||||
Tiles::canUpgradeStraightRail(tileClassId) &&
|
||||
(tileRotate == rotate || (tileRotate + TileRotate::Deg180) == rotate) &&
|
||||
deleteTile(x, y))
|
||||
{
|
||||
tileClassId = Bridge45LeftRailTile::classId;
|
||||
rotate = tileRotate;
|
||||
}
|
||||
else if((tileRotate == rotate - TileRotate::Deg45 || tileRotate == rotate - TileRotate::Deg225) && deleteTile(x, y))
|
||||
{
|
||||
tileClassId = Bridge45RightRailTile::classId;
|
||||
rotate = tileRotate;
|
||||
// tileClassId and rotate are ok :)
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
class BufferStopRailTile : public RailTile
|
||||
{
|
||||
CLASS_ID("board_tile.rail.bufferstop")
|
||||
CLASS_ID("board_tile.rail.buffer_stop")
|
||||
CREATE(BufferStopRailTile)
|
||||
|
||||
public:
|
||||
|
||||
@ -52,3 +52,13 @@ std::shared_ptr<Tile> Tiles::create(const std::shared_ptr<World>& world, std::st
|
||||
IF_CLASSID_CREATE(TunnelRailTile)
|
||||
return std::shared_ptr<Tile>();
|
||||
}
|
||||
|
||||
bool Tiles::canUpgradeStraightRail(std::string_view classId)
|
||||
{
|
||||
return
|
||||
(classId == TunnelRailTile::classId) ||
|
||||
(classId == BlockRailTile::classId) ||
|
||||
(classId == SensorRailTile::classId) ||
|
||||
(classId == Signal2AspectRailTile::classId) ||
|
||||
(classId == Signal3AspectRailTile::classId);
|
||||
}
|
||||
|
||||
@ -82,6 +82,8 @@ struct Tiles
|
||||
);
|
||||
|
||||
static std::shared_ptr<Tile> create(const std::shared_ptr<World>& world, std::string_view classId, std::string_view id = {});
|
||||
|
||||
static bool canUpgradeStraightRail(std::string_view classId);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
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.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -22,7 +22,8 @@
|
||||
|
||||
#include "abstractmethod.hpp"
|
||||
|
||||
AbstractMethod::AbstractMethod(Object& object, const std::string& name) :
|
||||
InterfaceItem(object, name)
|
||||
AbstractMethod::AbstractMethod(Object& object, const std::string& name, MethodFlags flags)
|
||||
: InterfaceItem(object, name)
|
||||
, m_flags{flags}
|
||||
{
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -24,13 +24,17 @@
|
||||
#define TRAINTASTIC_SERVER_CORE_ABSTRACTMETHOD_HPP
|
||||
|
||||
#include "interfaceitem.hpp"
|
||||
#include "methodflags.hpp"
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <stdexcept>
|
||||
#include "objectptr.hpp"
|
||||
#include "argument.hpp"
|
||||
|
||||
class AbstractMethod : public InterfaceItem
|
||||
{
|
||||
private:
|
||||
const MethodFlags m_flags;
|
||||
|
||||
public:
|
||||
class MethodCallError : public std::runtime_error
|
||||
{
|
||||
@ -91,15 +95,18 @@ class AbstractMethod : public InterfaceItem
|
||||
}
|
||||
};
|
||||
|
||||
using Argument = std::variant<bool, int64_t, double, std::string, ObjectPtr>;
|
||||
using Result = std::variant<std::monostate, bool, int64_t, double, std::string, ObjectPtr>;
|
||||
|
||||
AbstractMethod(Object& object, const std::string& name);
|
||||
AbstractMethod(Object& object, const std::string& name, MethodFlags m_flags = noMethodFlags);
|
||||
|
||||
inline bool isScriptCallable() const { return m_flags == MethodFlags::ScriptCallable; }
|
||||
|
||||
inline MethodFlags flags() const { return m_flags; }
|
||||
|
||||
virtual std::size_t argumentCount() const = 0;
|
||||
virtual std::vector<ValueType> argumentTypes() const = 0;
|
||||
virtual ValueType resultType() const = 0;
|
||||
virtual Result call(const std::vector<Argument>& args) = 0;
|
||||
virtual Result call(const Arguments& args) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -24,8 +24,9 @@
|
||||
#include "../core/idobject.hpp"
|
||||
#include "../world/worldloader.hpp"
|
||||
|
||||
AbstractObjectList::AbstractObjectList(Object& _parent, const std::string& parentPropertyName) :
|
||||
SubObject{_parent, parentPropertyName}
|
||||
AbstractObjectList::AbstractObjectList(Object& _parent, const std::string& parentPropertyName)
|
||||
: SubObject{_parent, parentPropertyName}
|
||||
, length{this, "length", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::NoScript}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
#include "subobject.hpp"
|
||||
#include "table.hpp"
|
||||
#include "property.hpp"
|
||||
|
||||
class WorldSaver;
|
||||
|
||||
@ -40,7 +41,11 @@ class AbstractObjectList : public SubObject, public Table
|
||||
virtual void setItems(const std::vector<ObjectPtr>& items) = 0;
|
||||
|
||||
public:
|
||||
Property<uint32_t> length;
|
||||
|
||||
AbstractObjectList(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
virtual ObjectPtr getObject(uint32_t index) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
35
server/src/core/argument.hpp
Normale Datei
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(is_access_valid(flags));
|
||||
assert(is_store_valid(flags));
|
||||
assert(isScriptValid(flags));
|
||||
}
|
||||
|
||||
void changed();
|
||||
@ -63,6 +64,17 @@ class BaseProperty : public InterfaceItem
|
||||
return (m_flags & PropertyFlagsStoreMask) == PropertyFlags::StoreState;
|
||||
}
|
||||
|
||||
bool isScriptReadable() const
|
||||
{
|
||||
const PropertyFlags scriptFlags = m_flags & PropertyFlagsScriptMask;
|
||||
return scriptFlags == PropertyFlags::ScriptReadOnly || scriptFlags == PropertyFlags::ScriptReadWrite;
|
||||
}
|
||||
|
||||
bool isScriptWriteable() const
|
||||
{
|
||||
return (m_flags & PropertyFlagsScriptMask) == PropertyFlags::ScriptReadWrite;
|
||||
}
|
||||
|
||||
PropertyFlags flags() const
|
||||
{
|
||||
return m_flags;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -34,6 +34,12 @@ TableModelPtr ControllerListBase::getModel()
|
||||
return std::make_shared<ControllerListBaseTableModel>(*this);
|
||||
}
|
||||
|
||||
ObjectPtr ControllerListBase::getObject(uint32_t index)
|
||||
{
|
||||
assert(index < m_items.size());
|
||||
return std::static_pointer_cast<Object>(m_items[index]);
|
||||
}
|
||||
|
||||
void ControllerListBase::add(ObjectPtr controller)
|
||||
{
|
||||
assert(controller);
|
||||
|
||||
@ -54,6 +54,8 @@ class ControllerListBase : public AbstractObjectList
|
||||
ControllerListBase(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
TableModelPtr getModel() final;
|
||||
|
||||
ObjectPtr getObject(uint32_t index) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
83
server/src/core/event.hpp
Normale Datei
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) :
|
||||
Object{},
|
||||
m_world{world},
|
||||
id{this, "id", std::string(_id.data(), _id.size()), PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
id{this, "id", std::string(_id.data(), _id.size()), PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly,
|
||||
[this](const std::string& value)
|
||||
{
|
||||
idChanged(value);
|
||||
|
||||
@ -44,7 +44,7 @@ template<std::size_t N, class... A>
|
||||
using getArgumentType = typename std::tuple_element<N, std::tuple<A...>>::type;
|
||||
|
||||
template<std::size_t N, class... A>
|
||||
auto getArgument(const AbstractMethod::Argument& value)
|
||||
auto getArgument(const Argument& value)
|
||||
{
|
||||
using T = std::remove_const_t<std::remove_reference_t<getArgumentType<N, A...>>>;
|
||||
|
||||
@ -96,6 +96,12 @@ class Method<R(A...)> : public AbstractMethod
|
||||
std::function<R(A...)> m_function;
|
||||
|
||||
public:
|
||||
Method(Object& object, const std::string& name, MethodFlags flags, std::function<R(A...)> function) :
|
||||
AbstractMethod(object, name, flags),
|
||||
m_function{std::move(function)}
|
||||
{
|
||||
}
|
||||
|
||||
Method(Object& object, const std::string& name, std::function<R(A...)> function) :
|
||||
AbstractMethod(object, name),
|
||||
m_function{std::move(function)}
|
||||
|
||||
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; \
|
||||
std::string_view getClassId() const override { return classId; }
|
||||
|
||||
template<class... Args> class Event;
|
||||
class AbstractMethod;
|
||||
class BaseProperty;
|
||||
class AbstractProperty;
|
||||
@ -55,6 +56,12 @@ class Object : public std::enable_shared_from_this<Object>
|
||||
protected:
|
||||
InterfaceItems m_interfaceItems;
|
||||
|
||||
template<class... Args>
|
||||
inline void fireEvent(Event<Args...>& event, Args... args)
|
||||
{
|
||||
event.fire(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline bool dying() const noexcept { return m_dying; }
|
||||
virtual void destroying() {}
|
||||
virtual void load(WorldLoader& loader, const nlohmann::json& data);
|
||||
|
||||
@ -76,18 +76,15 @@ class ObjectList : public AbstractObjectList
|
||||
}
|
||||
|
||||
public:
|
||||
Property<uint32_t> length;
|
||||
|
||||
ObjectList(Object& _parent, const std::string& parentPropertyName) :
|
||||
AbstractObjectList{_parent, parentPropertyName},
|
||||
length{this, "length", 0, PropertyFlags::ReadOnly}
|
||||
AbstractObjectList{_parent, parentPropertyName}
|
||||
{
|
||||
}
|
||||
|
||||
inline const_iterator begin() const noexcept { return m_items.begin(); }
|
||||
inline const_iterator end() const noexcept { return m_items.end(); }
|
||||
|
||||
ObjectPtr getObject(uint32_t index)
|
||||
ObjectPtr getObject(uint32_t index) final
|
||||
{
|
||||
assert(index < m_items.size());
|
||||
return std::static_pointer_cast<Object>(m_items[index]);
|
||||
|
||||
@ -260,7 +260,7 @@ bool Session::processMessage(const Message& message)
|
||||
const ValueType resultType = message.read<ValueType>();
|
||||
const uint8_t argumentCount = message.read<uint8_t>();
|
||||
|
||||
std::vector<AbstractMethod::Argument> args;
|
||||
Arguments args;
|
||||
for(uint8_t i = 0; i < argumentCount; i++)
|
||||
{
|
||||
switch(message.read<ValueType>())
|
||||
|
||||
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.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -23,6 +23,7 @@
|
||||
#include "class.hpp"
|
||||
#include "push.hpp"
|
||||
#include "object.hpp"
|
||||
#include "checkarguments.hpp"
|
||||
|
||||
#include "../board/board.hpp"
|
||||
#include "../board/boardlist.hpp"
|
||||
@ -96,78 +97,132 @@ static bool isInstance(const ::ObjectPtr& object)
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline static void setField(lua_State* L, std::string_view key)
|
||||
inline static void registerValue(lua_State* L, std::string_view key)
|
||||
{
|
||||
static_assert(std::is_base_of_v<::Object, T>);
|
||||
push(L, key);
|
||||
|
||||
// add to global class (used by Class::push)
|
||||
lua_getglobal(L, metaTableName);
|
||||
Lua::push(L, T::classId);
|
||||
*reinterpret_cast<IsInstance*>(lua_newuserdata(L, sizeof(IsInstance))) = isInstance<T>;
|
||||
luaL_newmetatable(L, metaTableName);
|
||||
if(luaL_newmetatable(L, metaTableName))
|
||||
{
|
||||
lua_pushcfunction(L, Class::__tostring);
|
||||
lua_setfield(L, -2, "__tostring");
|
||||
}
|
||||
lua_setmetatable(L, -2);
|
||||
lua_rawset(L, -3);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// add to sandbox class global:
|
||||
Lua::push(L, key);
|
||||
Class::push<T>(L);
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
|
||||
void Class::registerValues(lua_State* L)
|
||||
{
|
||||
assert(lua_istable(L, -1));
|
||||
assert(lua_istable(L, -1)); // sandboxes global class
|
||||
|
||||
setField<Board>(L, "BOARD");
|
||||
setField<BoardList>(L, "BOARD_LIST");
|
||||
lua_newtable(L); // global
|
||||
lua_setglobal(L, metaTableName);
|
||||
|
||||
setField<StraightRailTile>(L, "STRAIGHT_RAIL_TILE");
|
||||
setField<TunnelRailTile>(L, "TUNNEL_RAIL_TILE");
|
||||
setField<BufferStopRailTile>(L, "BUFFER_STOP_RAIL_TILE");
|
||||
setField<Curve45RailTile>(L, "CURVE_45_RAIL_TILE");
|
||||
setField<Curve90RailTile>(L, "CURVE_90_RAIL_TILE");
|
||||
setField<Cross45RailTile>(L, "CROSS_45_RAIL_TILE");
|
||||
setField<Cross90RailTile>(L, "CROSS_90_RAIL_TILE");
|
||||
setField<Bridge45RightRailTile>(L, "BRIDGE_45_RIGHT_RAIL_TILE");
|
||||
setField<Bridge45LeftRailTile>(L, "BRIDGE_45_LEFT_RAIL_TILE");
|
||||
setField<Bridge90RailTile>(L, "BRIDGE_90_RAIL_TILE");
|
||||
setField<TurnoutSingleSlipRailTile>(L, "TURNOUT_SINGLE_SLIP_RAIL_TILE");
|
||||
setField<TurnoutLeft90RailTile>(L, "TURNOUT_LEFT_90_RAIL_TILE");
|
||||
setField<TurnoutRight45RailTile>(L, "TURNOUT_RIGHT_45_RAIL_TILE");
|
||||
setField<TurnoutLeft45RailTile>(L, "TURNOUT_LEFT_45_RAIL_TILE");
|
||||
setField<TurnoutRight90RailTile>(L, "TURNOUT_RIGHT_90_RAIL_TILE");
|
||||
setField<TurnoutDoubleSlipRailTile>(L, "TURNOUT_DOUBLE_SLIP_RAIL_TILE");
|
||||
setField<TurnoutWyeRailTile>(L, "TURNOUT_WYE_RAIL_TILE");
|
||||
setField<TurnoutLeftCurvedRailTile>(L, "TURNOUT__RAIL_TILE");
|
||||
setField<Turnout3WayRailTile>(L, "TURNOUT_3WAY_RAIL_TILE");
|
||||
setField<TurnoutRightCurvedRailTile>(L, "TURNOUT_RIGHT_CURVED_RAIL_TILE");
|
||||
setField<Signal2AspectRailTile>(L, "SIGNAL_2_ASPECT_RAIL_TILE");
|
||||
setField<Signal3AspectRailTile>(L, "SIGNAL_3_ASPECT_RAIL_TILE");
|
||||
setField<SensorRailTile>(L, "SENSOR_RAIL_TILE");
|
||||
setField<BlockRailTile>(L, "BLOCK_RAIL_TILE");
|
||||
registerValue<Board>(L, "BOARD");
|
||||
registerValue<BoardList>(L, "BOARD_LIST");
|
||||
|
||||
setField<Clock>(L, "CLOCK");
|
||||
registerValue<StraightRailTile>(L, "STRAIGHT_RAIL_TILE");
|
||||
registerValue<TunnelRailTile>(L, "TUNNEL_RAIL_TILE");
|
||||
registerValue<BufferStopRailTile>(L, "BUFFER_STOP_RAIL_TILE");
|
||||
registerValue<Curve45RailTile>(L, "CURVE_45_RAIL_TILE");
|
||||
registerValue<Curve90RailTile>(L, "CURVE_90_RAIL_TILE");
|
||||
registerValue<Cross45RailTile>(L, "CROSS_45_RAIL_TILE");
|
||||
registerValue<Cross90RailTile>(L, "CROSS_90_RAIL_TILE");
|
||||
registerValue<Bridge45RightRailTile>(L, "BRIDGE_45_RIGHT_RAIL_TILE");
|
||||
registerValue<Bridge45LeftRailTile>(L, "BRIDGE_45_LEFT_RAIL_TILE");
|
||||
registerValue<Bridge90RailTile>(L, "BRIDGE_90_RAIL_TILE");
|
||||
registerValue<TurnoutSingleSlipRailTile>(L, "TURNOUT_SINGLE_SLIP_RAIL_TILE");
|
||||
registerValue<TurnoutLeft90RailTile>(L, "TURNOUT_LEFT_90_RAIL_TILE");
|
||||
registerValue<TurnoutRight45RailTile>(L, "TURNOUT_RIGHT_45_RAIL_TILE");
|
||||
registerValue<TurnoutLeft45RailTile>(L, "TURNOUT_LEFT_45_RAIL_TILE");
|
||||
registerValue<TurnoutRight90RailTile>(L, "TURNOUT_RIGHT_90_RAIL_TILE");
|
||||
registerValue<TurnoutDoubleSlipRailTile>(L, "TURNOUT_DOUBLE_SLIP_RAIL_TILE");
|
||||
registerValue<TurnoutWyeRailTile>(L, "TURNOUT_WYE_RAIL_TILE");
|
||||
registerValue<TurnoutLeftCurvedRailTile>(L, "TURNOUT__RAIL_TILE");
|
||||
registerValue<Turnout3WayRailTile>(L, "TURNOUT_3WAY_RAIL_TILE");
|
||||
registerValue<TurnoutRightCurvedRailTile>(L, "TURNOUT_RIGHT_CURVED_RAIL_TILE");
|
||||
registerValue<Signal2AspectRailTile>(L, "SIGNAL_2_ASPECT_RAIL_TILE");
|
||||
registerValue<Signal3AspectRailTile>(L, "SIGNAL_3_ASPECT_RAIL_TILE");
|
||||
registerValue<SensorRailTile>(L, "SENSOR_RAIL_TILE");
|
||||
registerValue<BlockRailTile>(L, "BLOCK_RAIL_TILE");
|
||||
|
||||
setField<DecoderFunction>(L, "DECODER_FUNCTION");
|
||||
setField<DecoderList>(L, "DECODER_LIST");
|
||||
setField<Decoder>(L, "DECODER");
|
||||
setField<DecoderFunctions>(L, "DECODER_FUNCTIONS");
|
||||
registerValue<Clock>(L, "CLOCK");
|
||||
|
||||
setField<Input>(L, "INPUT");
|
||||
setField<InputList>(L, "INPUT_LIST");
|
||||
registerValue<DecoderFunction>(L, "DECODER_FUNCTION");
|
||||
registerValue<DecoderList>(L, "DECODER_LIST");
|
||||
registerValue<Decoder>(L, "DECODER");
|
||||
registerValue<DecoderFunctions>(L, "DECODER_FUNCTIONS");
|
||||
|
||||
setField<Output>(L, "OUTPUT");
|
||||
setField<OutputList>(L, "OUTPUT_LIST");
|
||||
registerValue<Input>(L, "INPUT");
|
||||
registerValue<InputList>(L, "INPUT_LIST");
|
||||
|
||||
setField<RailVehicleList>(L, "RAIL_VEHICLE_LIST");
|
||||
setField<Locomotive>(L, "LOCOMOTIVE");
|
||||
setField<FreightCar>(L, "FREIGHT_CAR");
|
||||
registerValue<Output>(L, "OUTPUT");
|
||||
registerValue<OutputList>(L, "OUTPUT_LIST");
|
||||
|
||||
setField<Train>(L, "TRAIN");
|
||||
setField<TrainList>(L, "TRAIN_LIST");
|
||||
registerValue<RailVehicleList>(L, "RAIL_VEHICLE_LIST");
|
||||
registerValue<Locomotive>(L, "LOCOMOTIVE");
|
||||
registerValue<FreightCar>(L, "FREIGHT_CAR");
|
||||
|
||||
setField<World>(L, "WORLD");
|
||||
registerValue<Train>(L, "TRAIN");
|
||||
registerValue<TrainList>(L, "TRAIN_LIST");
|
||||
|
||||
registerValue<World>(L, "WORLD");
|
||||
}
|
||||
|
||||
int Class::isInstance(lua_State* L)
|
||||
void Class::push(lua_State* L, std::string_view classId)
|
||||
{
|
||||
if(lua_gettop(L) != 2)
|
||||
errorExpectedNArgumentsGotN(L, 2, lua_gettop(L));
|
||||
lua_getglobal(L, metaTableName);
|
||||
assert(lua_istable(L, -1));
|
||||
Lua::push(L, classId);
|
||||
lua_rawget(L, -2);
|
||||
assert(lua_isuserdata(L, -1));
|
||||
lua_insert(L, lua_gettop(L) - 1);
|
||||
lua_pop(L, 1);
|
||||
assert(lua_isuserdata(L, -1));
|
||||
}
|
||||
|
||||
push(L, (*reinterpret_cast<IsInstance*>(luaL_checkudata(L, 2, metaTableName)))(Object::test(L, 1)));
|
||||
void Class::push(lua_State* L, const ObjectPtr& object)
|
||||
{
|
||||
push(L, object->getClassId());
|
||||
}
|
||||
|
||||
int Class::__tostring(lua_State* L)
|
||||
{
|
||||
Sandbox::getGlobal(L, metaTableName);
|
||||
|
||||
// get the real table, the global is wrapped for write protection:
|
||||
lua_getmetatable(L, -1);
|
||||
lua_getfield(L, -1, "__index");
|
||||
assert(lua_istable(L, -1));
|
||||
|
||||
// loop over table to find value and the return key
|
||||
const int idx = lua_gettop(L);
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L, idx))
|
||||
{
|
||||
const bool eq = lua_compare(L, 1, -1, LUA_OPEQ);
|
||||
lua_pop(L, 1); // pop value
|
||||
if(eq)
|
||||
return 1;
|
||||
}
|
||||
|
||||
lua_pushlstring(L, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Class::getClass(lua_State* L)
|
||||
{
|
||||
checkArguments(L, 1);
|
||||
push(L, Object::check(L, 1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#ifndef TRAINTASTIC_SERVER_LUA_CLASS_HPP
|
||||
#define TRAINTASTIC_SERVER_LUA_CLASS_HPP
|
||||
|
||||
#include <string_view>
|
||||
#include <lua.hpp>
|
||||
#include "../core/objectptr.hpp"
|
||||
|
||||
@ -32,7 +33,19 @@ struct Class
|
||||
{
|
||||
static void registerValues(lua_State* L);
|
||||
|
||||
static int isInstance(lua_State* L);
|
||||
static void push(lua_State* L, std::string_view classId);
|
||||
static void push(lua_State* L, const ObjectPtr& object);
|
||||
|
||||
template<class T>
|
||||
static void push(lua_State* L)
|
||||
{
|
||||
static_assert(std::is_base_of_v<::Object, T>);
|
||||
push(L, T::classId);
|
||||
}
|
||||
|
||||
static int __tostring(lua_State* L);
|
||||
|
||||
static int getClass(lua_State* L);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -32,6 +32,15 @@
|
||||
|
||||
namespace Lua {
|
||||
|
||||
inline void pushEnum(lua_State* L, const char* enumName, lua_Integer value)
|
||||
{
|
||||
lua_getglobal(L, enumName); // get tabel with all enum values: key=int, value=userdata enum
|
||||
assert(lua_istable(L, -1)); // check if enum is registered
|
||||
lua_rawgeti(L, -1, value); // get userdata by key
|
||||
lua_insert(L, lua_gettop(L) - 1); // swap table and userdata
|
||||
lua_pop(L, 1); // remove table
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct Enum
|
||||
{
|
||||
@ -53,11 +62,7 @@ struct Enum
|
||||
|
||||
static void push(lua_State* L, T value)
|
||||
{
|
||||
lua_getglobal(L, EnumName<T>::value); // get tabel with all enum values: key=int, value=userdata enum
|
||||
assert(lua_istable(L, -1)); // check if enum is registered
|
||||
lua_rawgeti(L, -1, static_cast<lua_Integer>(value)); // get userdata by key
|
||||
lua_insert(L, lua_gettop(L) - 1); // swap table and userdata
|
||||
lua_pop(L, 1); // remove table
|
||||
pushEnum(L, EnumName<T>::value, static_cast<lua_Integer>(value));
|
||||
}
|
||||
|
||||
static int __tostring(lua_State* L)
|
||||
|
||||
@ -37,12 +37,16 @@ namespace Lua {
|
||||
[[noreturn]] inline void errorCantSetNonExistingProperty(lua_State* L) { luaL_error(L, "can't set non existing property"); abort(); }
|
||||
[[noreturn]] inline void errorCantSetReadOnlyProperty(lua_State* L) { luaL_error(L, "can't set read only property"); abort(); }
|
||||
|
||||
[[noreturn]] inline void errorDeadEvent(lua_State* L) { luaL_error(L, "dead event"); abort(); }
|
||||
[[noreturn]] inline void errorDeadMethod(lua_State* L) { luaL_error(L, "dead method"); abort(); }
|
||||
[[noreturn]] inline void errorDeadObject(lua_State* L) { luaL_error(L, "dead object"); abort(); }
|
||||
|
||||
[[noreturn]] inline void errorExpectedNArgumentsGotN(lua_State* L, int expected, int got) { luaL_error(L, "expected %d arguments, got %d", expected, got); abort(); }
|
||||
[[noreturn]] inline void errorExpectedNNArgumentsGotN(lua_State* L, int min, int max, int got) { luaL_error(L, "expected %d..%d arguments, got %d", min, max, got); abort(); }
|
||||
|
||||
[[noreturn]] inline void errorException(lua_State* L, const std::exception& e) { luaL_error(L, "exceptiop: %s", e.what()); abort(); }
|
||||
[[noreturn]] inline void errorException(lua_State* L, const std::exception& e) { luaL_error(L, "exception: %s", e.what()); abort(); }
|
||||
|
||||
[[noreturn]] inline void errorGlobalNIsReadOnly(lua_State* L, const char* name) { luaL_error(L, "global %s is readonly", name); abort(); }
|
||||
|
||||
[[noreturn]] inline void errorInternal(lua_State* L) { luaL_error(L, "internal error"); abort(); }
|
||||
|
||||
|
||||
134
server/src/lua/event.cpp
Normale Datei
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 "object.hpp"
|
||||
#include "method.hpp"
|
||||
#include "event.hpp"
|
||||
#include "../log/log.hpp"
|
||||
|
||||
namespace Lua {
|
||||
@ -79,6 +80,8 @@ int Log::log(lua_State* L, LogMessage code)
|
||||
message += object->getClassId();
|
||||
else if(Method::test(L, i))
|
||||
message += "method";
|
||||
else if(Event::test(L, i))
|
||||
message += "event";
|
||||
else
|
||||
{
|
||||
lua_getglobal(L, "tostring");
|
||||
|
||||
@ -91,7 +91,7 @@ int Method::__call(lua_State* L)
|
||||
if(lua_gettop(L) - 1 != argc)
|
||||
errorExpectedNArgumentsGotN(L, argc, lua_gettop(L) - 1);
|
||||
|
||||
std::vector<AbstractMethod::Argument> args;
|
||||
Arguments args;
|
||||
args.reserve(argc);
|
||||
|
||||
|
||||
|
||||
@ -25,33 +25,61 @@
|
||||
#include "check.hpp"
|
||||
#include "to.hpp"
|
||||
#include "method.hpp"
|
||||
#include "event.hpp"
|
||||
#include "error.hpp"
|
||||
#include "../core/object.hpp"
|
||||
#include "../core/abstractproperty.hpp"
|
||||
#include "../core/abstractmethod.hpp"
|
||||
#include "../core/abstractevent.hpp"
|
||||
|
||||
namespace Lua {
|
||||
|
||||
ObjectPtr Object::check(lua_State* L, int index)
|
||||
{
|
||||
ObjectPtrWeak& data = **static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableName));
|
||||
if(ObjectPtr object = data.lock())
|
||||
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
|
||||
if(!data)
|
||||
data = static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableName));
|
||||
|
||||
if(ObjectPtr object = (**data).lock())
|
||||
return object;
|
||||
else
|
||||
errorDeadObject(L);
|
||||
}
|
||||
|
||||
std::shared_ptr<AbstractObjectList> Object::checkList(lua_State* L, int index)
|
||||
{
|
||||
ObjectPtrWeak& data = **static_cast<ObjectPtrWeak**>(luaL_checkudata(L, index, metaTableNameList));
|
||||
if(ObjectPtr object = data.lock())
|
||||
return std::static_pointer_cast<AbstractObjectList>(object);
|
||||
else
|
||||
errorDeadObject(L);
|
||||
}
|
||||
|
||||
ObjectPtr Object::test(lua_State* L, int index)
|
||||
{
|
||||
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableName));
|
||||
if(!data)
|
||||
return ObjectPtr();
|
||||
data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
|
||||
|
||||
if(!data)
|
||||
return {};
|
||||
else if(ObjectPtr object = (**data).lock())
|
||||
return object;
|
||||
else
|
||||
errorDeadObject(L);
|
||||
}
|
||||
|
||||
std::shared_ptr<AbstractObjectList> Object::testList(lua_State* L, int index)
|
||||
{
|
||||
ObjectPtrWeak** data = static_cast<ObjectPtrWeak**>(luaL_testudata(L, index, metaTableNameList));
|
||||
if(!data)
|
||||
return {};
|
||||
else if(ObjectPtr object = (**data).lock())
|
||||
return std::static_pointer_cast<AbstractObjectList>(object);
|
||||
else
|
||||
errorDeadObject(L);
|
||||
}
|
||||
|
||||
void Object::push(lua_State* L, const ObjectPtr& value)
|
||||
{
|
||||
if(value)
|
||||
@ -62,7 +90,10 @@ void Object::push(lua_State* L, const ObjectPtr& value)
|
||||
{
|
||||
lua_pop(L, 1); // remove nil
|
||||
*static_cast<ObjectPtrWeak**>(lua_newuserdata(L, sizeof(ObjectPtrWeak*))) = new ObjectPtrWeak(value);
|
||||
luaL_setmetatable(L, metaTableName);
|
||||
if(dynamic_cast<AbstractObjectList*>(value.get()))
|
||||
luaL_setmetatable(L, metaTableNameList);
|
||||
else
|
||||
luaL_setmetatable(L, metaTableName);
|
||||
lua_pushvalue(L, -1); // copy userdata on stack
|
||||
lua_rawsetp(L, -3, value.get()); // add object to table
|
||||
}
|
||||
@ -85,6 +116,18 @@ void Object::registerType(lua_State* L)
|
||||
lua_setfield(L, -2, "__newindex");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// metatable for object list userdata:
|
||||
luaL_newmetatable(L, metaTableNameList);
|
||||
lua_pushcfunction(L, __gc);
|
||||
lua_setfield(L, -2, "__gc");
|
||||
lua_pushcfunction(L, __index);
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_pushcfunction(L, __newindex);
|
||||
lua_setfield(L, -2, "__newindex");
|
||||
lua_pushcfunction(L, __len);
|
||||
lua_setfield(L, -2, "__len");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// weak table for object userdata:
|
||||
lua_newtable(L);
|
||||
lua_newtable(L); // metatable
|
||||
@ -104,47 +147,89 @@ int Object::__gc(lua_State* L)
|
||||
int Object::__index(lua_State* L)
|
||||
{
|
||||
ObjectPtr object{check(L, 1)};
|
||||
|
||||
// handle list[index]:
|
||||
{
|
||||
lua_Integer index;
|
||||
if(to(L, 2, index))
|
||||
{
|
||||
if(auto list = std::dynamic_pointer_cast<AbstractObjectList>(object))
|
||||
{
|
||||
if(index >= 1 && index <= list->length)
|
||||
push(L, list->getObject(static_cast<uint32_t>(index - 1)));
|
||||
else
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view name{to<std::string_view>(L, 2)};
|
||||
|
||||
if(InterfaceItem* item = object->getItem(name))
|
||||
{
|
||||
// TODO: test scriptable
|
||||
|
||||
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(item))
|
||||
{
|
||||
switch(property->type())
|
||||
if(property->isScriptReadable())
|
||||
{
|
||||
case ValueType::Boolean:
|
||||
Lua::push(L, property->toBool());
|
||||
break;
|
||||
switch(property->type())
|
||||
{
|
||||
case ValueType::Boolean:
|
||||
Lua::push(L, property->toBool());
|
||||
break;
|
||||
|
||||
//case ValueType::Enum:
|
||||
case ValueType::Enum:
|
||||
// EnumName<T>::value assigned to the std::string_view is NUL terminated,
|
||||
// so it can be used as const char* however it is a bit tricky :)
|
||||
assert(*(property->enumName().data() + property->enumName().size()) == '\0');
|
||||
pushEnum(L, property->enumName().data(), static_cast<lua_Integer>(property->toInt64()));
|
||||
break;
|
||||
|
||||
case ValueType::Integer:
|
||||
Lua::push(L, property->toInt64());
|
||||
break;
|
||||
case ValueType::Integer:
|
||||
Lua::push(L, property->toInt64());
|
||||
break;
|
||||
|
||||
case ValueType::Float:
|
||||
Lua::push(L, property->toDouble());
|
||||
break;
|
||||
case ValueType::Float:
|
||||
Lua::push(L, property->toDouble());
|
||||
break;
|
||||
|
||||
case ValueType::String:
|
||||
Lua::push(L, property->toString());
|
||||
break;
|
||||
case ValueType::String:
|
||||
Lua::push(L, property->toString());
|
||||
break;
|
||||
|
||||
case ValueType::Object:
|
||||
push(L, property->toObject());
|
||||
break;
|
||||
case ValueType::Object:
|
||||
push(L, property->toObject());
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
lua_pushnil(L);
|
||||
break;
|
||||
case ValueType::Set:
|
||||
// set_name<T>::value assigned to the std::string_view is NUL terminated,
|
||||
// so it can be used as const char* however it is a bit tricky :)
|
||||
assert(*(property->setName().data() + property->setName().size()) == '\0');
|
||||
pushSet(L, property->setName().data(), static_cast<lua_Integer>(property->toInt64()));
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
lua_pushnil(L);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
lua_pushnil(L);
|
||||
}
|
||||
else if(AbstractMethod* method = dynamic_cast<AbstractMethod*>(item))
|
||||
{
|
||||
Method::push(L, *method);
|
||||
if(method->isScriptCallable())
|
||||
Method::push(L, *method);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
}
|
||||
else if(auto* event = dynamic_cast<AbstractEvent*>(item))
|
||||
{
|
||||
if(event->isScriptable())
|
||||
Event::push(L, *event);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -165,9 +250,7 @@ int Object::__newindex(lua_State* L)
|
||||
|
||||
if(AbstractProperty* property = object->getProperty(name))
|
||||
{
|
||||
// TODO: test scriptable
|
||||
|
||||
if(!property->isWriteable())
|
||||
if(!property->isScriptWriteable() || !property->isWriteable())
|
||||
errorCantSetReadOnlyProperty(L);
|
||||
|
||||
try
|
||||
@ -209,4 +292,13 @@ int Object::__newindex(lua_State* L)
|
||||
errorCantSetNonExistingProperty(L);
|
||||
}
|
||||
|
||||
int Object::__len(lua_State* L)
|
||||
{
|
||||
auto list{checkList(L, 1)};
|
||||
|
||||
Lua::push(L, list->length.value());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
#include <lua.hpp>
|
||||
#include <memory>
|
||||
#include "../core/objectptr.hpp"
|
||||
#include "../core/abstractobjectlist.hpp"
|
||||
|
||||
namespace Lua {
|
||||
|
||||
@ -35,12 +36,17 @@ class Object
|
||||
static int __gc(lua_State* L);
|
||||
static int __index(lua_State* L);
|
||||
static int __newindex(lua_State* L);
|
||||
static int __len(lua_State* L);
|
||||
|
||||
public:
|
||||
static constexpr char const* metaTableName = "object";
|
||||
static constexpr char const* metaTableNameList = "object_list";
|
||||
|
||||
static ObjectPtr check(lua_State* L, int index);
|
||||
static std::shared_ptr<AbstractObjectList> checkList(lua_State* L, int index);
|
||||
|
||||
static ObjectPtr test(lua_State* L, int index);
|
||||
static std::shared_ptr<AbstractObjectList> testList(lua_State* L, int index);
|
||||
|
||||
static void push(lua_State* L, const ObjectPtr& value);
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -24,22 +24,104 @@
|
||||
#include "push.hpp"
|
||||
#include "object.hpp"
|
||||
#include "method.hpp"
|
||||
#include "event.hpp"
|
||||
#include "eventhandler.hpp"
|
||||
#include "log.hpp"
|
||||
#include "class.hpp"
|
||||
#include "to.hpp"
|
||||
#include "type.hpp"
|
||||
#include <version.hpp>
|
||||
#include <traintastic/utils/str.hpp>
|
||||
#include <traintastic/codename.hpp>
|
||||
#include "../world/world.hpp"
|
||||
#include "../enum/decoderprotocol.hpp"
|
||||
#include "../enum/direction.hpp"
|
||||
#include "../enum/worldevent.hpp"
|
||||
#include "../enum/worldscale.hpp"
|
||||
#include "../set/worldstate.hpp"
|
||||
|
||||
#define LUA_SANDBOX "_sandbox"
|
||||
#define LUA_SANDBOX_GLOBALS "_sandbox_globals"
|
||||
|
||||
#define ADD_GLOBAL_TO_SANDBOX(x) \
|
||||
lua_pushliteral(L, x); \
|
||||
lua_getglobal(L, x); \
|
||||
lua_settable(L, -3);
|
||||
constexpr std::array<std::string_view, 23> readOnlyGlobals = {{
|
||||
// Lua baselib:
|
||||
"assert",
|
||||
"type",
|
||||
"pairs",
|
||||
"ipairs",
|
||||
"next",
|
||||
"tonumber",
|
||||
"tostring",
|
||||
"_G",
|
||||
// Lua libs:
|
||||
LUA_MATHLIBNAME,
|
||||
LUA_STRLIBNAME,
|
||||
LUA_TABLIBNAME,
|
||||
// Constants:
|
||||
"VERSION",
|
||||
"VERSION_MAJOR",
|
||||
"VERSION_MINOR",
|
||||
"VERSION_PATCH",
|
||||
"CODENAME",
|
||||
"LUA_VERSION",
|
||||
// Objects:
|
||||
"world",
|
||||
"log",
|
||||
// Functions:
|
||||
"is_instance",
|
||||
// Type info:
|
||||
"class",
|
||||
"enum",
|
||||
"set",
|
||||
}};
|
||||
|
||||
static void addExtensions(lua_State* L, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions)
|
||||
{
|
||||
assert(lua_istable(L, -1));
|
||||
for(auto [name, func] : extensions)
|
||||
{
|
||||
lua_pushcfunction(L, func);
|
||||
lua_setfield(L, -2, name);
|
||||
}
|
||||
}
|
||||
|
||||
static void addBaseLib(lua_State* L, std::initializer_list<const char*> names, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions = {})
|
||||
{
|
||||
// load Lua baselib:
|
||||
lua_pushcfunction(L, luaopen_base);
|
||||
lua_pushliteral(L, "");
|
||||
lua_call(L, 1, 0);
|
||||
|
||||
for(const char* name : names)
|
||||
{
|
||||
lua_getglobal(L, name);
|
||||
assert(!lua_isnil(L, -1));
|
||||
lua_setfield(L, -2, name);
|
||||
}
|
||||
|
||||
addExtensions(L, extensions);
|
||||
}
|
||||
|
||||
static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunction, std::initializer_list<const char*> names, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions = {})
|
||||
{
|
||||
lua_createtable(L, 0, names.size() + extensions.size());
|
||||
|
||||
luaL_requiref(L, libraryName, openFunction, 1);
|
||||
|
||||
for(const char* name : names)
|
||||
{
|
||||
lua_getfield(L, -1, name);
|
||||
assert(!lua_isnil(L, -1));
|
||||
lua_setfield(L, -3, name);
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop lib
|
||||
|
||||
addExtensions(L, extensions);
|
||||
|
||||
Lua::ReadOnlyTable::wrap(L, -1);
|
||||
lua_setfield(L, -2, libraryName);
|
||||
}
|
||||
|
||||
namespace Lua {
|
||||
|
||||
@ -49,15 +131,33 @@ void Sandbox::close(lua_State* L)
|
||||
lua_close(L);
|
||||
}
|
||||
|
||||
int Sandbox::__index(lua_State* L)
|
||||
{
|
||||
lua_getglobal(L, LUA_SANDBOX_GLOBALS);
|
||||
lua_replace(L, 1);
|
||||
|
||||
lua_rawget(L, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Sandbox::__newindex(lua_State* L)
|
||||
{
|
||||
if(std::find(readOnlyGlobals.begin(), readOnlyGlobals.end(), to<std::string_view>(L, 2)) != readOnlyGlobals.end())
|
||||
errorGlobalNIsReadOnly(L, lua_tostring(L, 2));
|
||||
|
||||
lua_getglobal(L, LUA_SANDBOX_GLOBALS);
|
||||
lua_replace(L, 1);
|
||||
|
||||
lua_rawset(L, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SandboxPtr Sandbox::create(Script& script)
|
||||
{
|
||||
lua_State* L = luaL_newstate();
|
||||
|
||||
// load Lua baselib:
|
||||
lua_pushcfunction(L, luaopen_base);
|
||||
lua_pushliteral(L, "");
|
||||
lua_call(L, 1, 0);
|
||||
|
||||
// create state data:
|
||||
*static_cast<StateData**>(lua_getextraspace(L)) = new StateData(script);
|
||||
|
||||
@ -65,27 +165,67 @@ SandboxPtr Sandbox::create(Script& script)
|
||||
Enum<DecoderProtocol>::registerType(L);
|
||||
Enum<Direction>::registerType(L);
|
||||
Enum<WorldEvent>::registerType(L);
|
||||
Enum<WorldScale>::registerType(L);
|
||||
Set<WorldState>::registerType(L);
|
||||
Object::registerType(L);
|
||||
Method::registerType(L);
|
||||
Event::registerType(L);
|
||||
|
||||
// setup sandbox:
|
||||
lua_newtable(L);
|
||||
luaL_newmetatable(L, LUA_SANDBOX);
|
||||
lua_pushcfunction(L, __index);
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_pushcfunction(L, __newindex);
|
||||
lua_setfield(L, -2, "__newindex");
|
||||
lua_setmetatable(L, -2);
|
||||
lua_setglobal(L, LUA_SANDBOX);
|
||||
|
||||
// add some Lua baselib functions to the sandbox:
|
||||
ADD_GLOBAL_TO_SANDBOX("assert")
|
||||
ADD_GLOBAL_TO_SANDBOX("type")
|
||||
ADD_GLOBAL_TO_SANDBOX("pairs")
|
||||
ADD_GLOBAL_TO_SANDBOX("ipairs")
|
||||
// setup globals:
|
||||
lua_newtable(L);
|
||||
|
||||
// add standard Lua lib functions and extensions/replacements:
|
||||
addBaseLib(L,
|
||||
{
|
||||
"assert", "pairs", "ipairs", "next", "tonumber", "tostring",
|
||||
},
|
||||
{
|
||||
{"type", type},
|
||||
{"get_class", Class::getClass},
|
||||
});
|
||||
addLib(L, LUA_MATHLIBNAME, luaopen_math, {
|
||||
"abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp",
|
||||
"floor", "fmod", "huge", "log", "max", "maxinteger", "min", "mininteger",
|
||||
"modf", "pi", "rad", "random", "randomseed", "sin", "sqrt", "tan",
|
||||
"tointeger", "type", "ult",
|
||||
});
|
||||
addLib(L, LUA_STRLIBNAME, luaopen_string, {
|
||||
"byte", "char", "find", "format", "gmatch", "gsub", "len", "lower",
|
||||
"match", "pack", "packsize", "rep", "reverse", "sub", "unpack", "upper",
|
||||
});
|
||||
addLib(L, LUA_TABLIBNAME, luaopen_table, {
|
||||
"concat", "insert", "pack", "unpack", "remove", "move", "sort",
|
||||
});
|
||||
|
||||
// set VERSION:
|
||||
lua_pushstring(L, STR(VERSION));
|
||||
lua_pushstring(L, TRAINTASTIC_VERSION);
|
||||
lua_setfield(L, -2, "VERSION");
|
||||
lua_pushinteger(L, TRAINTASTIC_VERSION_MAJOR);
|
||||
lua_setfield(L, -2, "VERSION_MAJOR");
|
||||
lua_pushinteger(L, TRAINTASTIC_VERSION_MINOR);
|
||||
lua_setfield(L, -2, "VERSION_MINOR");
|
||||
lua_pushinteger(L, TRAINTASTIC_VERSION_PATCH);
|
||||
lua_setfield(L, -2, "VERSION_PATCH");
|
||||
|
||||
// set CODENAME
|
||||
lua_pushstring(L, TRAINTASTIC_CODENAME);
|
||||
lua_setfield(L, -2, "CODENAME");
|
||||
|
||||
// set LUA_VERSION
|
||||
const std::string_view ident{lua_ident};
|
||||
push(L, ident.substr(13, ident.find('$', 13) - 14));
|
||||
lua_setfield(L, -2, "LUA_VERSION");
|
||||
|
||||
// add world:
|
||||
push(L, std::static_pointer_cast<::Object>(script.world().lock()));
|
||||
lua_setfield(L, -2, "world");
|
||||
@ -94,10 +234,6 @@ SandboxPtr Sandbox::create(Script& script)
|
||||
Log::push(L);
|
||||
lua_setfield(L, -2, "log");
|
||||
|
||||
// add is_instance function:
|
||||
lua_pushcfunction(L, Class::isInstance);
|
||||
lua_setfield(L, -2, "is_instance");
|
||||
|
||||
// add class types:
|
||||
lua_newtable(L);
|
||||
Class::registerValues(L);
|
||||
@ -109,6 +245,7 @@ SandboxPtr Sandbox::create(Script& script)
|
||||
Enum<DecoderProtocol>::registerValues(L);
|
||||
Enum<Direction>::registerValues(L);
|
||||
Enum<WorldEvent>::registerValues(L);
|
||||
Enum<WorldScale>::registerValues(L);
|
||||
ReadOnlyTable::wrap(L, -1);
|
||||
lua_setfield(L, -2, "enum");
|
||||
|
||||
@ -118,12 +255,11 @@ SandboxPtr Sandbox::create(Script& script)
|
||||
ReadOnlyTable::wrap(L, -1);
|
||||
lua_setfield(L, -2, "set");
|
||||
|
||||
// let global _G point to itself:
|
||||
lua_pushliteral(L, "_G");
|
||||
lua_pushvalue(L, -2);
|
||||
lua_settable(L, -3);
|
||||
// let global _G point to the sandbox:
|
||||
lua_getglobal(L, LUA_SANDBOX);
|
||||
lua_setfield(L, -2, "_G");
|
||||
|
||||
lua_setglobal(L, LUA_SANDBOX);
|
||||
lua_setglobal(L, LUA_SANDBOX_GLOBALS);
|
||||
|
||||
return SandboxPtr(L, close);
|
||||
}
|
||||
@ -135,7 +271,7 @@ Sandbox::StateData& Sandbox::getStateData(lua_State* L)
|
||||
|
||||
int Sandbox::getGlobal(lua_State* L, const char* name)
|
||||
{
|
||||
lua_getglobal(L, LUA_SANDBOX); // get the sandbox
|
||||
lua_getglobal(L, LUA_SANDBOX_GLOBALS); // get the sandbox
|
||||
lua_pushstring(L, name);
|
||||
const int type = lua_gettable(L, -2); // get item
|
||||
lua_insert(L, lua_gettop(L) - 1); // swap item and sandbox on the stack
|
||||
@ -167,4 +303,14 @@ int Sandbox::pcall(lua_State* L, int nargs, int nresults, int errfunc)
|
||||
return lua_pcall(L, nargs, nresults, errfunc);
|
||||
}
|
||||
|
||||
Sandbox::StateData::~StateData()
|
||||
{
|
||||
while(!m_eventHandlers.empty())
|
||||
{
|
||||
auto handler = m_eventHandlers.begin()->second;
|
||||
m_eventHandlers.erase(m_eventHandlers.begin());
|
||||
handler->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -24,11 +24,15 @@
|
||||
#define TRAINTASTIC_SERVER_LUA_SANDBOX_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <lua.hpp>
|
||||
|
||||
namespace Lua {
|
||||
|
||||
class Script;
|
||||
class EventHandler;
|
||||
|
||||
using SandboxPtr = std::unique_ptr<lua_State, void(*)(lua_State*)>;
|
||||
|
||||
@ -36,23 +40,66 @@ class Sandbox
|
||||
{
|
||||
private:
|
||||
static void close(lua_State* L);
|
||||
static int __index(lua_State* L);
|
||||
static int __newindex(lua_State* L);
|
||||
|
||||
public:
|
||||
class StateData
|
||||
{
|
||||
private:
|
||||
Script& m_script;
|
||||
lua_Integer m_eventHandlerId;
|
||||
std::map<lua_Integer, std::shared_ptr<EventHandler>> m_eventHandlers;
|
||||
|
||||
public:
|
||||
StateData(Script& script) :
|
||||
m_script{script}
|
||||
StateData(Script& script)
|
||||
: m_script{script}
|
||||
, m_eventHandlerId{1}
|
||||
{
|
||||
}
|
||||
|
||||
~StateData();
|
||||
|
||||
inline Script& script() const
|
||||
{
|
||||
return m_script;
|
||||
}
|
||||
|
||||
std::shared_ptr<EventHandler> getEventHandler(lua_Integer id) const
|
||||
{
|
||||
auto it = m_eventHandlers.find(id);
|
||||
if(it != m_eventHandlers.end())
|
||||
return it->second;
|
||||
else
|
||||
return std::shared_ptr<EventHandler>();
|
||||
}
|
||||
|
||||
inline lua_Integer registerEventHandler(std::shared_ptr<EventHandler> handler)
|
||||
{
|
||||
while(m_eventHandlers.find(m_eventHandlerId) != m_eventHandlers.end())
|
||||
{
|
||||
if(m_eventHandlerId == std::numeric_limits<lua_Integer>::max())
|
||||
m_eventHandlerId = 1;
|
||||
else
|
||||
m_eventHandlerId++;
|
||||
}
|
||||
const lua_Integer id = m_eventHandlerId;
|
||||
m_eventHandlerId++;
|
||||
m_eventHandlers.emplace(id, std::move(handler));
|
||||
return id;
|
||||
}
|
||||
|
||||
inline void unregisterEventHandler(const std::shared_ptr<EventHandler>& handler)
|
||||
{
|
||||
auto it = std::find_if(m_eventHandlers.begin(), m_eventHandlers.end(),
|
||||
[&handler](const auto& elem)
|
||||
{
|
||||
return elem.second == handler;
|
||||
});
|
||||
|
||||
if(it != m_eventHandlers.end())
|
||||
m_eventHandlers.erase(it);
|
||||
}
|
||||
};
|
||||
|
||||
static SandboxPtr create(Script& script);
|
||||
|
||||
@ -116,19 +116,6 @@ void Script::worldEvent(WorldState worldState, WorldEvent worldEvent)
|
||||
IdObject::worldEvent(worldState, worldEvent);
|
||||
|
||||
updateEnabled();
|
||||
|
||||
if(m_sandbox)
|
||||
{
|
||||
lua_State* L = m_sandbox.get();
|
||||
if(Sandbox::getGlobal(L, "world_event") == LUA_TFUNCTION)
|
||||
{
|
||||
push(L, worldState);
|
||||
push(L, worldEvent);
|
||||
pcall(L, 2);
|
||||
}
|
||||
else
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Script::updateEnabled()
|
||||
|
||||
@ -49,6 +49,23 @@ struct set_values
|
||||
template<typename T>
|
||||
constexpr auto set_values_v = set_values<T>::value;
|
||||
|
||||
inline void pushSet(lua_State* L, const char* setName, lua_Integer value)
|
||||
{
|
||||
lua_getglobal(L, setName); // get tabel with all values: key=int, value=set as userdata
|
||||
assert(lua_type(L, -1) == LUA_TTABLE);
|
||||
lua_rawgeti(L, -1, value); // get userdata
|
||||
if(lua_isnil(L, -1)) // value not in table
|
||||
{
|
||||
lua_pop(L, 1); // remove nil
|
||||
*static_cast<lua_Integer*>(lua_newuserdata(L, sizeof(value))) = value;
|
||||
luaL_setmetatable(L, setName);
|
||||
lua_pushvalue(L, -1); // copy set userdata on stack
|
||||
lua_rawseti(L, -3, value); // add set value to table
|
||||
}
|
||||
lua_insert(L, lua_gettop(L) - 1); // swap tabel and userdata
|
||||
lua_pop(L, 1); // remove tabel
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct Set
|
||||
{
|
||||
@ -57,32 +74,20 @@ struct Set
|
||||
|
||||
static T check(lua_State* L, int index)
|
||||
{
|
||||
return *static_cast<T*>(luaL_checkudata(L, index, set_name_v<T>));
|
||||
return static_cast<T>(*static_cast<lua_Integer*>(luaL_checkudata(L, index, set_name_v<T>)));
|
||||
}
|
||||
|
||||
static bool test(lua_State* L, int index, T& value)
|
||||
{
|
||||
T* data = static_cast<T*>(luaL_testudata(L, index, set_name_v<T>));
|
||||
lua_Integer* data = static_cast<lua_Integer*>(luaL_testudata(L, index, set_name_v<T>));
|
||||
if(data)
|
||||
value = *data;
|
||||
value = static_cast<T>(*data);
|
||||
return data;
|
||||
}
|
||||
|
||||
static void push(lua_State* L, T value)
|
||||
{
|
||||
lua_getglobal(L, set_name_v<T>); // get tabel with all values: key=int, value=set as userdata
|
||||
assert(lua_type(L, -1) == LUA_TTABLE);
|
||||
lua_rawgeti(L, -1, static_cast<lua_Integer>(value)); // get userdata
|
||||
if(lua_isnil(L, -1)) // value not in table
|
||||
{
|
||||
lua_pop(L, 1); // remove nil
|
||||
*static_cast<T*>(lua_newuserdata(L, sizeof(value))) = value;
|
||||
luaL_setmetatable(L, set_name_v<T>);
|
||||
lua_pushvalue(L, -1); // copy set userdata on stack
|
||||
lua_rawseti(L, -3, static_cast<lua_Integer>(value)); // add set value to table
|
||||
}
|
||||
lua_insert(L, lua_gettop(L) - 1); // swap tabel and userdata
|
||||
lua_pop(L, 1); // remove tabel
|
||||
pushSet(L, set_name_v<T>, static_cast<lua_Integer>(value));
|
||||
}
|
||||
|
||||
static int __add(lua_State* L)
|
||||
|
||||
80
server/src/lua/type.cpp
Normale Datei
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