Merge remote-tracking branch 'origin/master' into 144-add-zone-support

Dieser Commit ist enthalten in:
Reinder Feenstra 2024-10-26 21:32:15 +02:00
Commit dd1521a1db
112 geänderte Dateien mit 2997 neuen und 18695 gelöschten Zeilen

Datei anzeigen

@ -60,16 +60,6 @@ jobs:
build_deb: true
defines: "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
- name: "macos-12"
os: "macos-12"
generator: "Unix Makefiles"
arch: ""
target: traintastic-client
jobs: 3
build_type: Release
build_deb: false
defines: ""
- name: "macos-13"
os: "macos-13"
generator: "Unix Makefiles"
@ -90,6 +80,16 @@ jobs:
build_deb: false
defines: ""
- name: "macos-15"
os: "macos-15"
generator: "Unix Makefiles"
arch: ""
target: traintastic-client
jobs: 3
build_type: Release
build_deb: false
defines: ""
steps:
- uses: FranzDiebold/github-env-vars-action@v2
@ -261,18 +261,6 @@ jobs:
defines: "-DINSTALL_SYSTEMD_SERVICE=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
ccov: false
- name: "macos-12"
os: "macos-12"
generator: "Unix Makefiles"
arch: ""
toolset: ""
target: all
jobs: 3
build_type: Release
build_deb: false
defines: ""
ccov: false
- name: "macos-13"
os: "macos-13"
generator: "Unix Makefiles"
@ -297,6 +285,18 @@ jobs:
defines: ""
ccov: false
- name: "macos-15"
os: "macos-15"
generator: "Unix Makefiles"
arch: ""
toolset: ""
target: all
jobs: 3
build_type: Release
build_deb: false
defines: ""
ccov: false
steps:
- uses: FranzDiebold/github-env-vars-action@v2

3
.gitmodules vendored
Datei anzeigen

@ -1,3 +1,6 @@
[submodule "shared/data/lncv"]
path = shared/data/lncv
url = ../lncv.git
[submodule "server/thirdparty/catch2"]
path = server/thirdparty/catch2
url = https://github.com/catchorg/Catch2.git

Datei anzeigen

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="clear_persistent_variables.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="58.303571"
inkscape:cy="31.785714"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<path
style="fill:none;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 3.175,292.76665 -10e-8,-16.93334 c 2.0852342,1e-5 3.0686586,0 4.2333333,0 4.2333328,2.11667 4.2333328,6.35 0,9.525 H 3.1749999"
id="path3223"
sodipodi:nodetypes="ccccc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 13.758333,275.83331 17.991666,292.76665 22.225,275.83331"
id="path4097"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#cd5c5c;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.1166666,294.88331 23.283333,273.71665"
id="path1540"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#cd5c5c;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.1166666,273.71665 23.283333,294.88331"
id="path1542"
sodipodi:nodetypes="cc" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 3.7 KiB

Datei anzeigen

@ -93,5 +93,6 @@
<file>board_tile.misc.switch.svg</file>
<file>board_tile.misc.label.svg</file>
<file>zone.svg</file>
<file>clear_persistent_variables.svg</file>
</qresource>
</RCC>

Datei anzeigen

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="clear_persistent_variables.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="58.303571"
inkscape:cy="31.785714"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<path
style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 3.175,292.76665 -10e-8,-16.93334 c 2.0852342,1e-5 3.0686586,0 4.2333333,0 4.2333328,2.11667 4.2333328,6.35 0,9.525 H 3.1749999"
id="path3223"
sodipodi:nodetypes="ccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 13.758333,275.83331 17.991666,292.76665 22.225,275.83331"
id="path4097"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#b00020;stroke-width:2.11666667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.1166666,294.88331 23.283333,273.71665"
id="path1540"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#b00020;stroke-width:2.11666667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.1166666,273.71665 23.283333,294.88331"
id="path1542"
sodipodi:nodetypes="cc" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 3.7 KiB

Datei anzeigen

@ -67,5 +67,6 @@
<file>board_tile.misc.switch.svg</file>
<file>board_tile.misc.label.svg</file>
<file>zone.svg</file>
<file>clear_persistent_variables.svg</file>
</qresource>
</RCC>

Datei anzeigen

@ -32,6 +32,7 @@
#include "getboardcolorscheme.hpp"
#include "tilepainter.hpp"
#include "../network/board.hpp"
#include "../network/callmethod.hpp"
#include "../network/object.tpp"
#include "../network/object/blockrailtile.hpp"
#include "../network/object/nxbuttonrailtile.hpp"
@ -39,6 +40,7 @@
#include "../network/abstractvectorproperty.hpp"
#include "../utils/enum.hpp"
#include "../utils/rectf.hpp"
#include "../misc/mimedata.hpp"
#include "../settings/boardsettings.hpp"
QRect rectToViewport(const QRect& r, const int gridSize)
@ -84,6 +86,7 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
setFocusPolicy(Qt::StrongFocus);
setAcceptDrops(true);
if(Q_LIKELY(m_boardLeft))
connect(m_boardLeft, &AbstractProperty::valueChanged, this, &BoardAreaWidget::updateMinimumSize);
@ -837,6 +840,65 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
}
}
void BoardAreaWidget::dragEnterEvent(QDragEnterEvent *event)
{
if(event->mimeData()->hasFormat(AssignTrainMimeData::mimeType))
{
m_dragMoveTileLocation = TileLocation::invalid;
event->acceptProposedAction();
}
}
void BoardAreaWidget::dragMoveEvent(QDragMoveEvent* event)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const TileLocation l = pointToTileLocation(event->pos());
#else
const TileLocation l = pointToTileLocation(event->position().toPoint());
#endif
if(m_dragMoveTileLocation != l)
{
m_dragMoveTileLocation = l;
if(event->mimeData()->hasFormat(AssignTrainMimeData::mimeType) &&
m_board.board().getTileId(l) == TileId::RailBlock)
{
return event->accept();
}
event->ignore();
}
}
void BoardAreaWidget::dropEvent(QDropEvent* event)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const TileLocation l = pointToTileLocation(event->pos());
#else
const TileLocation l = pointToTileLocation(event->position().toPoint());
#endif
switch(m_board.board().getTileId(l))
{
case TileId::RailBlock:
if(auto* assignTrain = dynamic_cast<const AssignTrainMimeData*>(event->mimeData()))
{
if(auto tile = std::dynamic_pointer_cast<BlockRailTile>(m_board.board().getTileObject(l)))
{
if(auto* method = tile->getMethod("assign_train"))
{
callMethod(*method, nullptr, assignTrain->trainId());
return event->accept();
}
}
}
break;
default:
break;
}
event->ignore();
}
void BoardAreaWidget::settingsChanged()
{
const auto& s = BoardSettings::instance();

Datei anzeigen

@ -89,6 +89,8 @@ class BoardAreaWidget : public QWidget
uint8_t m_mouseMoveTileWidthMax;
uint8_t m_mouseMoveTileHeightMax;
TileLocation m_dragMoveTileLocation;
inline int boardLeft() const { return Q_LIKELY(m_boardLeft) ? m_boardLeft->toInt() - boardMargin : 0; }
inline int boardTop() const { return Q_LIKELY(m_boardTop) ? m_boardTop->toInt() - boardMargin: 0; }
inline int boardRight() const { return Q_LIKELY(m_boardRight) ? m_boardRight->toInt() + boardMargin: 0; }
@ -115,6 +117,9 @@ class BoardAreaWidget : public QWidget
void mouseMoveEvent(QMouseEvent* event) final;
void wheelEvent(QWheelEvent* event) final;
void paintEvent(QPaintEvent* event) final;
void dragEnterEvent(QDragEnterEvent* event) final;
void dragMoveEvent(QDragMoveEvent* event) final;
void dropEvent(QDropEvent* event) final;
protected slots:
void settingsChanged();

Datei anzeigen

@ -774,7 +774,7 @@ void TilePainter::drawTriangle(const QRectF& r)
{r.right(), r.bottom()},
{r.left(), r.bottom()}}};
m_painter.drawConvexPolygon(points.data(), points.size());
m_painter.drawConvexPolygon(points.data(), static_cast<int>(points.size()));
}
void TilePainter::drawLED(const QRectF& r, const QColor& color, const QColor& borderColor)

Datei anzeigen

@ -171,7 +171,6 @@ MainWindow::MainWindow(QWidget* parent) :
if(const ObjectPtr& traintastic = m_connection->traintastic())
traintastic->callMethod("close_world");
});
m_actionCloseWorld->setShortcut(QKeySequence::Close);
menu->addSeparator();
m_actionImportWorld = menu->addAction(Theme::getIcon("world_import"), Locale::tr("qtapp.mainmenu:import_world") + "...",
[this]()

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 Reinder Feenstra
* Copyright (C) 2022-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -32,6 +32,7 @@
#include "../settings/statusbarsettings.hpp"
#include "../widget/status/interfacestatuswidget.hpp"
#include "../widget/status/luastatuswidget.hpp"
#include "../widget/status/simulationstatuswidget.hpp"
MainWindowStatusBar::MainWindowStatusBar(MainWindow& mainWindow)
: QStatusBar(&mainWindow)
@ -148,6 +149,8 @@ void MainWindowStatusBar::updateStatuses()
m_statuses->layout()->addWidget(new InterfaceStatusWidget(object, this));
else if(object->classId() == "status.lua")
m_statuses->layout()->addWidget(new LuaStatusWidget(object, this));
else if(object->classId() == "status.simulation")
m_statuses->layout()->addWidget(new SimulationStatusWidget(object, this));
}
});
}

44
client/src/misc/mimedata.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,44 @@
/**
* client/src/misc/mimedata.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_MISC_MIMEDATA_HPP
#define TRAINTASTIC_CLIENT_MISC_MIMEDATA_HPP
#include <QMimeData>
class AssignTrainMimeData : public QMimeData
{
public:
inline static const auto mimeType = QLatin1String("application/vnd.traintastic.assign_train");
explicit AssignTrainMimeData(const QString& trainId)
{
setData(mimeType, trainId.toUtf8());
}
inline QString trainId() const
{
return QString::fromUtf8(data(mimeType));
}
};
#endif

Datei anzeigen

@ -70,7 +70,7 @@ class ServerLogTableModel final : public QAbstractTableModel
ServerLogTableModel(std::shared_ptr<Connection> connection);
~ServerLogTableModel();
int columnCount(const QModelIndex& parent = QModelIndex()) const final { Q_UNUSED(parent); return m_columnHeaders.size(); }
int columnCount(const QModelIndex& parent = QModelIndex()) const final { Q_UNUSED(parent); return static_cast<int>(m_columnHeaders.size()); }
int rowCount(const QModelIndex& parent = QModelIndex()) const final;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const final;

Datei anzeigen

@ -24,6 +24,7 @@
#include "list/marklincanlocomotivelistwidget.hpp"
#include "objectlist/boardlistwidget.hpp"
#include "objectlist/throttleobjectlistwidget.hpp"
#include "objectlist/trainlistwidget.hpp"
#include "object/luascripteditwidget.hpp"
#include "object/objecteditwidget.hpp"
#include "object/itemseditwidget.hpp"
@ -47,7 +48,7 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
if(classId == "command_station_list")
return new ObjectListWidget(object, parent); // todo remove
else if(classId == "decoder_list" || classId == "list.train")
else if(classId == "decoder_list")
return new ThrottleObjectListWidget(object, parent); // todo remove
else if(classId == "controller_list")
return new ObjectListWidget(object, parent); // todo remove
@ -61,6 +62,10 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
{
return new BoardListWidget(object, parent);
}
if(classId == "list.train")
{
return new TrainListWidget(object, parent);
}
else if(object->classId().startsWith("list."))
return new ObjectListWidget(object, parent);
else if(classId == "lua.script")

Datei anzeigen

@ -160,7 +160,7 @@ void InputMonitorWidget::keyReleaseEvent(QKeyEvent* event)
uint32_t InputMonitorWidget::pageCount() const
{
return static_cast<uint32_t>(m_addressMax->toInt64() - m_addressMin->toInt64() + m_leds.size()) / m_leds.size();
return static_cast<uint32_t>(m_addressMax->toInt64() - m_addressMin->toInt64() + m_leds.size()) / static_cast<uint32_t>(m_leds.size());
}
void InputMonitorWidget::setPage(uint32_t value)
@ -184,7 +184,7 @@ void InputMonitorWidget::setGroupBy(uint32_t value)
LEDWidget* InputMonitorWidget::getLED(uint32_t address)
{
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size();
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size());
if(address >= first && (address - first) < m_leds.size())
return m_leds[address - first];
@ -199,7 +199,7 @@ void InputMonitorWidget::updateLEDs()
const uint32_t addressMin = static_cast<uint32_t>(m_addressMin->toInt64());
const uint32_t addressMax = static_cast<uint32_t>(m_addressMax->toInt64());
uint32_t address = addressMin + m_page * m_leds.size();
uint32_t address = addressMin + m_page * static_cast<uint32_t>(m_leds.size());
for(auto* led : m_leds)
{

Datei anzeigen

@ -29,6 +29,7 @@
#include <version.hpp>
#include <traintastic/locale/locale.hpp>
#include <traintastic/utils/standardpaths.hpp>
#include "../../misc/methodaction.hpp"
#include "../../network/object.hpp"
#include "../../network/property.hpp"
#include "../../network/method.hpp"
@ -108,6 +109,17 @@ void LuaScriptEditWidget::buildForm()
m_stop->setEnabled(value.toBool());
});
if(auto* method = m_object->getMethod("clear_persistent_variables"))
{
QWidget* spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->show();
toolbar->addWidget(spacer);
toolbar->addAction(new MethodAction(Theme::getIcon("clear_persistent_variables"), *method, this));
}
QWidget* spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->show();

Datei anzeigen

@ -399,6 +399,16 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object_, QWidget* parent) :
}
}
if(auto* method = object()->getMethod("clear_persistent_variables"))
{
QWidget* spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->show();
m_toolbar->addWidget(spacer);
m_toolbar->addAction(new MethodAction(Theme::getIcon("clear_persistent_variables"), *method, this));
}
if(!m_toolbar->actions().empty())
{
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, m_toolbar);

Datei anzeigen

@ -0,0 +1,41 @@
/**
* client/src/widget/objectlist/trainlistwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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 "trainlistwidget.hpp"
#include <QDrag>
#include "../tablewidget.hpp"
#include "../../misc/mimedata.hpp"
TrainListWidget::TrainListWidget(const ObjectPtr& object, QWidget* parent)
: ThrottleObjectListWidget(object, parent)
{
connect(m_tableWidget, &TableWidget::rowDragged,
[this](int row)
{
if(auto trainId = m_tableWidget->getRowObjectId(row); !trainId.isEmpty())
{
QDrag* drag = new QDrag(m_tableWidget);
drag->setMimeData(new AssignTrainMimeData(trainId));
drag->exec(Qt::CopyAction);
}
});
}

Datei anzeigen

@ -1,9 +1,9 @@
/**
* server/src/enum/commandstationstatus.hpp
* client/src/widget/objectlist/trainlistwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 Reinder Feenstra
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -20,9 +20,15 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ENUM_COMMANDSTATIONSTATUS_HPP
#define TRAINTASTIC_SERVER_ENUM_COMMANDSTATIONSTATUS_HPP
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_TRAINLISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_TRAINLISTWIDGET_HPP
#include <traintastic/enum/commandstationstatus.hpp>
#include "throttleobjectlistwidget.hpp"
class TrainListWidget : public ThrottleObjectListWidget
{
public:
explicit TrainListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
};
#endif

Datei anzeigen

@ -161,7 +161,7 @@ OutputKeyboardWidget::OutputKeyboardWidget(std::shared_ptr<OutputKeyboard> objec
connect(led, &LEDWidget::clicked, this,
[this, index=i]()
{
const uint32_t address = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size() / 2 + index / 2;
const uint32_t address = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size()) / 2 + index / 2;
const auto value = (index & 0x1) ? OutputPairValue::Second : OutputPairValue::First;
callMethod(*m_setOutputValue, nullptr, address, value);
});
@ -251,7 +251,7 @@ uint32_t OutputKeyboardWidget::pageCount() const
{
leds *= 2;
}
return static_cast<uint32_t>(leds + m_leds.size() - 1) / m_leds.size();
return static_cast<uint32_t>(leds + m_leds.size() - 1) / static_cast<uint32_t>(m_leds.size());
}
void OutputKeyboardWidget::setPage(uint32_t value)
@ -277,7 +277,7 @@ LEDWidget* OutputKeyboardWidget::getLED(uint32_t address)
{
assert(m_object->outputType() == OutputType::Single);
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size();
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size());
if(address >= first && (address - first) < m_leds.size())
return m_leds[address - first];
@ -289,7 +289,7 @@ std::pair<LEDWidget*, LEDWidget*> OutputKeyboardWidget::getLEDs(uint32_t address
{
assert(m_object->outputType() == OutputType::Pair);
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size() / 2;
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size()) / 2;
if(address >= first && (address - first) < m_leds.size())
return {m_leds[(address - first) * 2], m_leds[(address - first) * 2 + 1]};
@ -309,7 +309,7 @@ void OutputKeyboardWidget::updateLEDs()
{
case OutputType::Single:
{
uint32_t address = addressMin + m_page * m_leds.size();
uint32_t address = addressMin + m_page * static_cast<uint32_t>(m_leds.size());
for(auto* led : m_leds)
{
const auto& outputState = m_object->getOutputState(address);
@ -329,7 +329,7 @@ void OutputKeyboardWidget::updateLEDs()
}
case OutputType::Pair:
{
uint32_t address = addressMin + m_page * m_leds.size() / 2;
uint32_t address = addressMin + m_page * static_cast<uint32_t>(m_leds.size()) / 2;
bool second = false;
for(auto* led : m_leds)
{

Datei anzeigen

@ -162,7 +162,7 @@ OutputMapWidget::~OutputMapWidget()
void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
{
m_table->setRowCount(items.size());
m_table->setRowCount(static_cast<int>(items.size()));
m_itemObjects = items;
m_actions.resize(items.size());
for(size_t i = 0; i < items.size(); i++)
@ -197,7 +197,7 @@ void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
assert(false);
text = "?";
}
m_table->setItem(i, columnKey, new QTableWidgetItem(text));
m_table->setItem(static_cast<int>(i), columnKey, new QTableWidgetItem(text));
}
if(m_hasUseColumn)
@ -211,16 +211,16 @@ void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
l->setAlignment(Qt::AlignCenter);
l->addWidget(new PropertyCheckBox(*p, w));
w->setLayout(l);
m_table->setCellWidget(i, columnUse, w);
m_table->setCellWidget(static_cast<int>(i), columnUse, w);
}
}
if(auto* p = items[i]->getProperty("visible"))
{
m_table->setRowHidden(i, !p->toBool());
m_table->setRowHidden(static_cast<int>(i), !p->toBool());
connect(p, &Property::valueChangedBool, this,
[this, row=i](bool value)
[this, row=static_cast<int>(i)](bool value)
{
m_table->setRowHidden(row, !value);
});
@ -228,10 +228,10 @@ void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
if(auto* outputActions = dynamic_cast<ObjectVectorProperty*>(items[i]->getVectorProperty("output_actions")))
{
updateTableOutputActions(*outputActions, i);
updateTableOutputActions(*outputActions, static_cast<int>(i));
connect(outputActions, &ObjectVectorProperty::valueChanged, this,
[this, row=i]()
[this, row=static_cast<int>(i)]()
{
updateTableOutputActions(*dynamic_cast<ObjectVectorProperty*>(sender()), row);
});
@ -291,7 +291,7 @@ void OutputMapWidget::updateKeyIcons()
break; // tileId not supported (yet)
}
m_table->item(i, columnKey)->setIcon(QPixmap::fromImage(image));
m_table->item(static_cast<int>(i), columnKey)->setIcon(QPixmap::fromImage(image));
}
}
}

Datei anzeigen

@ -189,6 +189,7 @@ PropertyLuaCodeEdit::Highlighter::Highlighter(QTextDocument* parent) :
QStringLiteral("\\bworld(?=\\.)"),
QStringLiteral("\\bset(?=\\.)"),
QStringLiteral("\\benum(?=\\.)"),
QStringLiteral("\\bpv(?=\\.)"),
};
for(const auto& regex : globals)
m_rules.append(Rule(regex, QColor(0xFF, 0x8C, 0x00)));

Datei anzeigen

@ -0,0 +1,66 @@
/**
* client/src/widget/status/simulationstatuswidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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 "simulationstatuswidget.hpp"
#include <QResizeEvent>
#include <traintastic/locale/locale.hpp>
#include "../../network/object.hpp"
#include "../../network/property.hpp"
#include "../../theme/theme.hpp"
SimulationStatusWidget::SimulationStatusWidget(const ObjectPtr& object, QWidget* parent)
: QSvgWidget(parent)
, m_object{object}
{
assert(m_object);
assert(m_object->classId() == "status.simulation");
load(Theme::getIconFile("simulation"));
if(auto* property = m_object->getProperty("label"))
{
connect(property, &Property::valueChanged, this, &SimulationStatusWidget::labelChanged);
}
labelChanged();
}
void SimulationStatusWidget::labelChanged()
{
QString label;
if(auto* property = m_object->getProperty("label"))
{
label = Locale::instance->parse(property->toString());
}
setToolTip(label);
}
void SimulationStatusWidget::resizeEvent(QResizeEvent* event)
{
QSvgWidget::resizeEvent(event);
// force same width as height:
setMinimumWidth(event->size().height());
setMaximumWidth(event->size().height());
}

Datei anzeigen

@ -0,0 +1,44 @@
/**
* client/src/widget/status/simulationstatuswidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_WIDGET_STATUS_SIMULATIONSTATUSWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_STATUS_SIMULATIONSTATUSWIDGET_HPP
#include <QSvgWidget>
#include "../../network/objectptr.hpp"
class SimulationStatusWidget : public QSvgWidget
{
private:
ObjectPtr m_object;
void labelChanged();
void stateChanged();
protected:
void resizeEvent(QResizeEvent* event) override;
public:
explicit SimulationStatusWidget(const ObjectPtr& object, QWidget* parent = nullptr);
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -24,6 +24,8 @@
#include <QHeaderView>
#include <QScrollBar>
#include <QSettings>
#include <QMouseEvent>
#include <QApplication>
#include "../network/tablemodel.hpp"
TableWidget::TableWidget(QWidget* parent) :
@ -113,3 +115,23 @@ void TableWidget::updateRegion()
m_model->setRegion(columnMin, columnMax, rowMin, rowMax);
}
void TableWidget::mouseMoveEvent(QMouseEvent* event)
{
QTableView::mouseMoveEvent(event);
if(event->button() == Qt::LeftButton)
{
m_dragStartPosition = event->pos();
}
}
void TableWidget::mousePressEvent(QMouseEvent* event)
{
QTableView::mousePressEvent(event);
if((event->buttons() & Qt::LeftButton) && (event->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance())
{
emit rowDragged(indexAt(m_dragStartPosition).row());
}
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023 Reinder Feenstra
* Copyright (C) 2019-2020,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -33,6 +33,10 @@ class TableWidget : public QTableView
protected:
TableModelPtr m_model;
int m_selectedRow = -1;
QPoint m_dragStartPosition;
void mouseMoveEvent(QMouseEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
protected slots:
void updateRegion();
@ -44,6 +48,9 @@ class TableWidget : public QTableView
QString getRowObjectId(int row) const;
void setTableModel(const TableModelPtr& model);
signals:
void rowDragged(int row);
};
#endif

Datei anzeigen

@ -20,6 +20,7 @@ class LuaDoc:
DEFAULT_LANGUAGE = 'en-us'
FILENAME_INDEX = 'index.html'
FILENAME_GLOBALS = 'globals.html'
FILENAME_PV = 'pv.html'
FILENAME_ENUM = 'enum.html'
FILENAME_SET = 'set.html'
FILENAME_OBJECT = 'object.html'
@ -30,6 +31,7 @@ class LuaDoc:
version = None
def __init__(self, project_root: str) -> None:
self._project_root = project_root
self._globals = LuaDoc._find_globals(project_root)
self._enums = LuaDoc._find_enums(project_root)
self._sets = LuaDoc._find_sets(project_root)
@ -101,6 +103,16 @@ class LuaDoc:
for object in self._objects:
if object['lua_name'] == id:
return '<a href="' + object['filename'] + fragment + '">' + (self._get_term(object['name']) if title == '' else title) + '</a>'
elif id == 'globals':
return '<a href="' + self.FILENAME_GLOBALS + fragment + '">' + (self._get_term('globals:title') if title == '' else title) + '</a>'
elif id == 'enum':
return '<a href="' + self.FILENAME_ENUM + fragment + '">' + (self._get_term('enum:title') if title == '' else title) + '</a>'
elif id == 'set':
return '<a href="' + self.FILENAME_SET + fragment + '">' + (self._get_term('set:title') if title == '' else title) + '</a>'
elif id == 'object':
return '<a href="' + self.FILENAME_OBJECT + fragment + '">' + (self._get_term('object:title') if title == '' else title) + '</a>'
elif id == 'pv':
return '<a href="' + self.FILENAME_PV + fragment + '">' + (self._get_term('pv:title') if title == '' else title) + '</a>'
return '<span style="color:red">' + m.group(0) + '</span>'
@ -447,6 +459,7 @@ class LuaDoc:
self._build_index(output_dir)
self._build_globals(output_dir, nav)
self._build_pv(output_dir, nav)
self._build_enums(output_dir, nav)
self._build_sets(output_dir, nav)
for _, lib in self._libs.items():
@ -649,6 +662,30 @@ class LuaDoc:
html += self._build_items_html(self._globals, 'globals.')
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_GLOBALS), self._add_toc(html))
def _build_pv(self, output_dir: str, nav: list) -> None:
title = self._get_term('pv:title')
html = self._get_header(title, nav + [{'title': title, 'href': LuaDoc.FILENAME_PV}])
html += '<p>' + self._get_term('pv:paragraph_1') + '</p>' + os.linesep
html += '<p>' + self._get_term('pv:paragraph_2') + '</p>' + os.linesep
html += '<h2 id="storing">' + self._get_term('pv.storing:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.storing:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'storingpersistentdata.lua'))) + '</code></pre>'
html += '<h2 id="retrieving">' + self._get_term('pv.retrieving:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.retrieving:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'retrievingpersistentdata.lua'))) + '</code></pre>'
html += '<h2 id="deleting">' + self._get_term('pv.deleting:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.deleting:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'deletingpersistentdata.lua'))) + '</code></pre>'
html += '<h2 id="checking">' + self._get_term('pv.checking:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.checking:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'checkingforpersistentdata.lua'))) + '</code></pre>'
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_PV), self._add_toc(html))
def _build_enums(self, output_dir: str, nav: list) -> None:
title = self._get_term('enum:title')
nav_enums = nav + [{'title': title, 'href': LuaDoc.FILENAME_ENUM}]
@ -854,6 +891,7 @@ class LuaDoc:
def _get_header(self, title: str, nav: list) -> str:
menu = ' <li><a href="' + LuaDoc.FILENAME_GLOBALS + '">' + self._get_term('globals:title') + '</a></li>' + os.linesep
menu += ' <li><a href="' + LuaDoc.FILENAME_PV + '">' + self._get_term('pv:title') + '</a></li>' + os.linesep
for k in sorted(list(self._libs.keys()) + ['enum', 'set']):
if k == 'enum':
menu += ' <li><a href="' + LuaDoc.FILENAME_ENUM + '">' + self._get_term('enum:title') + '</a></li>' + os.linesep

Datei anzeigen

@ -0,0 +1,6 @@
if pv.freight_car_1 == nil then
pv.freight_car_1 = {
cargo = 'none',
destination = 'unset'
}
end

Datei anzeigen

@ -0,0 +1,4 @@
pv.number = nil
pv.title = nil
pv.very_cool = nil
pv.freight_car_1 = nil

Datei anzeigen

@ -0,0 +1,9 @@
log.debug(pv.number)
log.debug(pv.title)
log.debug(pv.very_cool)
log.debug(pv.freight_car_1.cargo)
for k, v in pairs(pv['freight_car_1']) do
log.debug(k, v)
end

Datei anzeigen

@ -0,0 +1,8 @@
pv.number = 42
pv.title = 'Traintastic is awesome!'
pv.very_cool = true
pv['freight_car_1'] = {
cargo = 'grain',
destination = 'upper yard'
}

Datei anzeigen

@ -67,6 +67,10 @@
"type": "constant",
"since": "0.1"
},
"pv": {
"type": "object",
"since": "0.3"
},
"world": {
"type": "object",
"since": "0.1"
@ -102,4 +106,4 @@
"type": "library",
"since": "0.1"
}
}
}

Datei anzeigen

@ -8,6 +8,7 @@
"mode": {},
"mute": {},
"no_smoke": {},
"blocks": {},
"zones": {},
"on_block_assigned": {
"parameters": [

Datei anzeigen

@ -2218,5 +2218,53 @@
{
"term": "object.zone.trains:description",
"definition": "List of {ref:object.trainzonestatus|train zone status} objects of all trains that are entering, entered or leaving the zone."
},
{
"term": "globals.pv:description",
"definition": "The {ref:pv|persistent variable} table."
},
{
"term": "pv:title",
"definition": "Persistent variables"
},
{
"term": "pv:paragraph_1",
"definition": "Persistent variables allow you to store and retrieve data that remains available across multiple executions of the Lua script. This can be particularly useful for maintaining state information that needs to be retained beyond the current script's lifetime."
},
{
"term": "pv:paragraph_2",
"definition": "The {ref:globals#pv|`pv`} global provides a simple and efficient interface for interacting with persistent data. Any values stored in {ref:globals#pv|`pv`} are saved across script executions and world save and loads. Below is a detailed breakdown of how to use the {ref:globals#pv|`pv`} global."
},
{
"term": "pv.storing:title",
"definition": "Storing persistent data"
},
{
"term": "pv.storing:paragraph_1",
"definition": "You can store data in {ref:globals#pv|`pv`} just like you would with a regular Lua table. Supported data types are numbers, strings, booleans, tables, {ref:enum|enums}, {ref:set|sets}, {ref:object|objects} and object methods."
},
{
"term": "pv.retrieving:title",
"definition": "Retrieving persistent data"
},
{
"term": "pv.retrieving:paragraph_1",
"definition": "To retrieve a previously stored value, including tables, access the corresponding key in the {ref:globals#pv|`pv`} global:"
},
{
"term": "pv.deleting:title",
"definition": "Deleting persistent data"
},
{
"term": "pv.deleting:paragraph_1",
"definition": "To delete a stored persistent value, including tables, simply assign `nil` to the desired key:"
},
{
"term": "pv.checking:title",
"definition": "Checking for persistent data"
},
{
"term": "pv.checking:paragraph_1",
"definition": "To determine if a persistent variable has been set, use an `if` statement with `nil` checks. Variables in {ref:globals#pv|`pv`} that haven't been initialized or have been deleted will return `nil`. This pattern is useful for initializing default values or handling cases where the persistent variables are cleared."
}
]

Datei anzeigen

@ -25,7 +25,7 @@ def highlight_replace(code: str, css_class: str, clickable_links: bool = False)
def highlight_lua(code: str) -> str:
code = re.sub(r'\b(math|table|string|class|enum|set|log|world)\b', r'<span class="global">\1</span>', code) # globals
code = re.sub(r'\b(math|table|string|class|enum|set|log|world|pv)\b', r'<span class="global">\1</span>', code) # globals
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
code = re.sub(r'\b((|-|\+)[0-9]+(\\.[0-9]*|)((e|E)(|-|\+)[0-9]+|))\b', r'<span class="number">\1</span>', code) # numbers: infloat, decimal

Datei anzeigen

@ -32,6 +32,8 @@ target_include_directories(traintastic-server SYSTEM PRIVATE
thirdparty)
if(BUILD_TESTING)
add_subdirectory(thirdparty/catch2)
set_target_properties(Catch2 PROPERTIES CXX_STANDARD 17)
add_executable(traintastic-server-test test/main.cpp)
add_dependencies(traintastic-server-test traintastic-lang)
target_compile_definitions(traintastic-server-test PRIVATE -DTRAINTASTIC_TEST)
@ -42,6 +44,7 @@ if(BUILD_TESTING)
target_include_directories(traintastic-server-test SYSTEM PRIVATE
../shared/thirdparty
thirdparty)
target_link_libraries(traintastic-server-test PRIVATE Catch2::Catch2WithMain)
endif()
file(GLOB SOURCES
@ -217,6 +220,9 @@ if(LINUX)
if(BUILD_TESTING)
target_link_libraries(traintastic-server-test PRIVATE PkgConfig::LIBSYSTEMD)
endif()
else()
# Use inotify for monitoring serial ports:
list(APPEND SOURCES "src/os/linux/serialportlistimplinotify.hpp" "src/os/linux/serialportlistimplinotify.cpp")
endif()
else()
# socket CAN is only available on linux:
@ -459,8 +465,7 @@ endif()
if(BUILD_TESTING)
include(Catch)
target_include_directories(traintastic-server-test PRIVATE thirdparty/catch2)
catch_discover_tests(traintastic-server-test)
catch_discover_tests(traintastic-server-test DISCOVERY_MODE PRE_TEST)
endif()
### Doxygen ###

Datei anzeigen

@ -1,206 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
Catch
-----
This module defines a function to help use the Catch test framework.
The :command:`catch_discover_tests` discovers tests by asking the compiled test
executable to enumerate its tests. This does not require CMake to be re-run
when tests change. However, it may not work in a cross-compiling environment,
and setting test properties is less convenient.
This command is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each Catch test case. Note
that this is in some cases less efficient, as common set-up and tear-down logic
cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: catch_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
catch_discover_tests(target
[TEST_SPEC arg1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[TEST_LIST var]
[REPORTER reporter]
[OUTPUT_DIR dir]
[OUTPUT_PREFIX prefix}
[OUTPUT_SUFFIX suffix]
)
``catch_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--list-test-names-only`` argument. This ensures that the full
list of tests is obtained. Since test discovery occurs at build time, it is
not necessary to re-run CMake when the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the Catch executable, which must be a known CMake executable
target. CMake will substitute the location of the built executable when
running the test.
``TEST_SPEC arg1...``
Specifies test cases, wildcarded test cases, tags and tag expressions to
pass to the Catch executable with the ``--list-test-names-only`` argument.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``catch_discover_tests()`` but with different
``TEST_SPEC`` or ``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``catch_discover_tests``.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``catch_discover_tests()``.
Note that this variable is only available in CTest.
``REPORTER reporter``
Use the specified reporter when running the test case. The reporter will
be passed to the Catch executable as ``--reporter reporter``.
``OUTPUT_DIR dir``
If specified, the parameter is passed along as
``--out dir/<test_name>`` to Catch executable. The actual file name is the
same as the test name. This should be used instead of
``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output
when using parallel test execution.
``OUTPUT_PREFIX prefix``
May be used in conjunction with ``OUTPUT_DIR``.
If specified, ``prefix`` is added to each output file name, like so
``--out dir/prefix<test_name>``.
``OUTPUT_SUFFIX suffix``
May be used in conjunction with ``OUTPUT_DIR``.
If specified, ``suffix`` is added to each output file name, like so
``--out dir/<test_name>suffix``. This can be used to add a file extension to
the output e.g. ".xml".
#]=======================================================================]
#------------------------------------------------------------------------------
function(catch_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_SPEC=${_TEST_SPEC}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}"
-D "TEST_REPORTER=${_REPORTER}"
-D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
-D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
-D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
-D "CTEST_FILE=${ctest_tests_file}"
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if (NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR
"Cannot set more than one TEST_INCLUDE_FILE"
)
endif()
endif()
endfunction()
###############################################################################
set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
)

Datei anzeigen

@ -1,132 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(spec ${TEST_SPEC})
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(reporter ${TEST_REPORTER})
set(output_dir ${TEST_OUTPUT_DIR})
set(output_prefix ${TEST_OUTPUT_PREFIX})
set(output_suffix ${TEST_OUTPUT_SUFFIX})
set(script)
set(suite)
set(tests)
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
# Run test executable to get list of available tests
if(NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
)
endif()
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
OUTPUT_VARIABLE output
RESULT_VARIABLE result
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
)
# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
if(${result} EQUAL 0)
message(WARNING
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
)
elseif(${result} LESS 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${result}\n"
" Output: ${output}\n"
)
endif()
string(REPLACE "\n" ";" output "${output}")
# Run test executable to get list of available reporters
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-reporters
OUTPUT_VARIABLE reporters_output
RESULT_VARIABLE reporters_result
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
)
if(${reporters_result} EQUAL 0)
message(WARNING
"Test executable '${TEST_EXECUTABLE}' contains no reporters!\n"
)
elseif(${reporters_result} LESS 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${reporters_result}\n"
" Output: ${reporters_output}\n"
)
endif()
string(FIND "${reporters_output}" "${reporter}" reporter_is_valid)
if(reporter AND ${reporter_is_valid} EQUAL -1)
message(FATAL_ERROR
"\"${reporter}\" is not a valid reporter!\n"
)
endif()
# Prepare reporter
if(reporter)
set(reporter_arg "--reporter ${reporter}")
endif()
# Prepare output dir
if(output_dir AND NOT IS_ABSOLUTE ${output_dir})
set(output_dir "${TEST_WORKING_DIR}/${output_dir}")
if(NOT EXISTS ${output_dir})
file(MAKE_DIRECTORY ${output_dir})
endif()
endif()
# Parse output
foreach(line ${output})
set(test ${line})
# Escape characters in test case names that would be parsed by Catch2
set(test_name ${test})
foreach(char , [ ])
string(REPLACE ${char} "\\${char}" test_name ${test_name})
endforeach(char)
# ...add output dir
if(output_dir)
string(REGEX REPLACE "[^A-Za-z0-9_]" "_" test_name_clean ${test_name})
set(output_dir_arg "--out ${output_dir}/${output_prefix}${test_name_clean}${output_suffix}")
endif()
# ...and add to script
add_command(add_test
"${prefix}${test}${suffix}"
${TEST_EXECUTOR}
"${TEST_EXECUTABLE}"
"${test_name}"
${extra_args}
"${reporter_arg}"
"${output_dir_arg}"
)
add_command(set_tests_properties
"${prefix}${test}${suffix}"
PROPERTIES
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties}
)
list(APPEND tests "${prefix}${test}${suffix}")
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
add_command(set ${TEST_LIST} ${tests})
# Write CTest script
file(WRITE "${CTEST_FILE}" "${script}")

Datei anzeigen

@ -28,7 +28,7 @@
#include "../../../map/node.hpp"
#include "../../../../core/objectproperty.hpp"
#include "../../../../core/method.hpp"
#include "../../../../enum/turnoutposition.hpp"
#include <traintastic/enum/turnoutposition.hpp>
#include "../../../../hardware/output/map/turnoutoutputmap.hpp"
class BlockPath;

Datei anzeigen

@ -80,7 +80,7 @@ struct Attributes
}
template<size_t N>
static inline void addClassList(InterfaceItem& item, const std::array<std::string_view, N>& classList)
static inline void addClassList(InterfaceItem& item, tcb::span<const std::string_view, N> classList)
{
item.addAttribute(AttributeName::ClassList, classList);
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023-2024 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 "object.hpp"
#include "idobject.hpp"
#include "subobject.hpp"
#include "abstractevent.hpp"
#include "abstractmethod.hpp"
#include "abstractproperty.hpp"
#include "abstractobjectproperty.hpp"
@ -97,6 +98,16 @@ AbstractVectorProperty* Object::getVectorProperty(std::string_view name)
return dynamic_cast<AbstractVectorProperty*>(getItem(name));
}
const AbstractEvent* Object::getEvent(std::string_view name) const
{
return dynamic_cast<const AbstractEvent*>(getItem(name));
}
AbstractEvent* Object::getEvent(std::string_view name)
{
return dynamic_cast<AbstractEvent*>(getItem(name));
}
void Object::load(WorldLoader& loader, const nlohmann::json& data)
{
for(auto& [name, value] : data.items())

Datei anzeigen

@ -134,6 +134,8 @@ class Object : public std::enable_shared_from_this<Object>
AbstractObjectProperty* getObjectProperty(std::string_view name);
const AbstractVectorProperty* getVectorProperty(std::string_view name) const;
AbstractVectorProperty* getVectorProperty(std::string_view name);
const AbstractEvent* getEvent(std::string_view name) const;
AbstractEvent* getEvent(std::string_view name);
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023 Reinder Feenstra
* Copyright (C) 2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -46,7 +46,7 @@ InterfaceList::InterfaceList(Object& _parent, std::string_view parentPropertyNam
Attributes::addDisplayName(create, DisplayName::List::create);
Attributes::addEnabled(create, editable);
Attributes::addClassList(create, Interfaces::classList);
Attributes::addClassList(create, Interfaces::classList());
m_interfaceItems.add(create);
Attributes::addDisplayName(delete_, DisplayName::List::delete_);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2024 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,35 @@
#include "interfaces.hpp"
#include "../../utils/ifclassidcreate.hpp"
#include "../../world/world.hpp"
#include "../../utils/makearray.hpp"
#include "dccexinterface.hpp"
#include "ecosinterface.hpp"
#include "hsi88.hpp"
#include "loconetinterface.hpp"
#include "marklincaninterface.hpp"
#include "traintasticdiyinterface.hpp"
#include "withrottleinterface.hpp"
#include "wlanmausinterface.hpp"
#include "xpressnetinterface.hpp"
#include "z21interface.hpp"
tcb::span<const std::string_view> Interfaces::classList()
{
static constexpr auto classes = makeArray(
DCCEXInterface::classId,
ECoSInterface::classId,
HSI88Interface::classId,
LocoNetInterface::classId,
MarklinCANInterface::classId,
TraintasticDIYInterface::classId,
WiThrottleInterface::classId,
WlanMausInterface::classId,
XpressNetInterface::classId,
Z21Interface::classId
);
return classes;
}
std::shared_ptr<Interface> Interfaces::create(World& world, std::string_view classId, std::string_view id)
{

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2024 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,36 +24,12 @@
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP
#include "interface.hpp"
#include "../../utils/makearray.hpp"
#include "dccexinterface.hpp"
#include "ecosinterface.hpp"
#include "hsi88.hpp"
#include "loconetinterface.hpp"
#include "marklincaninterface.hpp"
#include "traintasticdiyinterface.hpp"
#include "withrottleinterface.hpp"
#include "wlanmausinterface.hpp"
#include "xpressnetinterface.hpp"
#include "z21interface.hpp"
struct Interfaces
{
static constexpr std::string_view classIdPrefix = "interface.";
static constexpr auto classList = makeArray(
DCCEXInterface::classId,
ECoSInterface::classId,
HSI88Interface::classId,
LocoNetInterface::classId,
MarklinCANInterface::classId,
TraintasticDIYInterface::classId,
WiThrottleInterface::classId,
WlanMausInterface::classId,
XpressNetInterface::classId,
Z21Interface::classId
);
static tcb::span<const std::string_view> classList();
static std::shared_ptr<Interface> create(World& world, std::string_view classId, std::string_view id = std::string_view{});
};

Datei anzeigen

@ -24,7 +24,7 @@
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_MAP_TURNOUTOUTPUTMAPITEM_HPP
#include "outputmapitembase.hpp"
#include "../../../enum/turnoutposition.hpp"
#include <traintastic/enum/turnoutposition.hpp>
class TurnoutOutputMapItem : public OutputMapItemBase<TurnoutPosition>
{

Datei anzeigen

@ -37,9 +37,9 @@
#include <traintastic/enum/outputpairvalue.hpp>
#include <traintastic/enum/textalign.hpp>
#include "../../src/enum/tristate.hpp"
#include "../../src/enum/turnoutposition.hpp"
#include <traintastic/enum/turnoutposition.hpp>
#include "../../src/enum/signalaspect.hpp"
#include "../../src/enum/worldevent.hpp"
#include <traintastic/enum/worldevent.hpp>
#include "../../src/enum/worldscale.hpp"
#include <traintastic/enum/zonetrainstate.hpp>
@ -81,6 +81,14 @@ struct Enums
if constexpr(sizeof...(Ts) != 0)
registerValues<Ts...>(L);
}
template<class... Ts>
inline static const std::array<std::string_view, sizeof...(Ts)> getMetaTableNames()
{
return std::array<std::string_view, sizeof...(Ts)>{EnumName<Ts>::value...};
}
inline static const auto metaTableNames = getMetaTableNames<LUA_ENUMS>();
};
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -39,6 +39,12 @@ namespace Lua {
[[noreturn]] inline void errorCantSetNonExistingProperty(lua_State* L) { luaL_error(L, "can't set non existing property"); abort(); }
[[noreturn]] inline void errorCantSetReadOnlyProperty(lua_State* L) { luaL_error(L, "can't set read only property"); abort(); }
[[noreturn]] inline void errorCantStoreValueAsPersistentVariableUnsupportedType(lua_State* L)
{
luaL_error(L, "can't store value as persistent variable, unsupported type");
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(); }
@ -50,6 +56,12 @@ namespace Lua {
[[noreturn]] inline void errorInternal(lua_State* L) { luaL_error(L, "internal error"); abort(); }
[[noreturn]] inline void errorTableContainsRecursion(lua_State* L)
{
luaL_error(L, "table contains recursion");
abort();
}
[[noreturn]] inline void errorTableIsReadOnly(lua_State* L) { luaL_error(L, "table is readonly"); abort(); }
}

Datei anzeigen

@ -31,6 +31,8 @@
namespace Lua {
constexpr char const* eventsGlobal = "events";
struct EventData
{
ObjectPtrWeak object;
@ -65,9 +67,18 @@ AbstractEvent* Event::test(lua_State* L, int index)
void Event::push(lua_State* L, AbstractEvent& value)
{
new(lua_newuserdata(L, sizeof(EventData))) EventData(value);
luaL_getmetatable(L, metaTableName);
lua_setmetatable(L, -2);
lua_getglobal(L, eventsGlobal);
lua_rawgetp(L, -1, &value);
if(lua_isnil(L, -1)) // event not in table
{
lua_pop(L, 1); // remove nil
new(lua_newuserdata(L, sizeof(EventData))) EventData(value);
luaL_setmetatable(L, metaTableName);
lua_pushvalue(L, -1); // copy userdata on stack
lua_rawsetp(L, -3, &value); // add event to table
}
lua_insert(L, lua_gettop(L) - 1); // swap table and userdata
lua_pop(L, 1); // remove table
}
void Event::registerType(lua_State* L)
@ -80,6 +91,15 @@ void Event::registerType(lua_State* L)
lua_pushcfunction(L, __gc);
lua_setfield(L, -2, "__gc");
lua_pop(L, 1);
// weak table for event userdata:
lua_newtable(L);
lua_newtable(L); // metatable
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "v");
lua_rawset(L, -3);
lua_setmetatable(L, -2);
lua_setglobal(L, eventsGlobal);
}
int Event::__index(lua_State* L)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2022-2023 Reinder Feenstra
* Copyright (C) 2019-2020,2022-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -30,6 +30,8 @@
namespace Lua {
constexpr char const* methodsGlobal = "methods";
struct MethodData
{
ObjectPtrWeak object;
@ -64,9 +66,18 @@ AbstractMethod* Method::test(lua_State* L, int index)
void Method::push(lua_State* L, AbstractMethod& value)
{
new(lua_newuserdata(L, sizeof(MethodData))) MethodData(value);
luaL_getmetatable(L, metaTableName);
lua_setmetatable(L, -2);
lua_getglobal(L, methodsGlobal);
lua_rawgetp(L, -1, &value);
if(lua_isnil(L, -1)) // method not in table
{
lua_pop(L, 1); // remove nil
new(lua_newuserdata(L, sizeof(MethodData))) MethodData(value);
luaL_setmetatable(L, metaTableName);
lua_pushvalue(L, -1); // copy userdata on stack
lua_rawsetp(L, -3, &value); // add method to table
}
lua_insert(L, lua_gettop(L) - 1); // swap table and userdata
lua_pop(L, 1); // remove table
}
void Method::registerType(lua_State* L)
@ -77,6 +88,15 @@ void Method::registerType(lua_State* L)
lua_pushcfunction(L, __call);
lua_setfield(L, -2, "__call");
lua_pop(L, 1);
// weak table for method userdata:
lua_newtable(L);
lua_newtable(L); // metatable
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "v");
lua_rawset(L, -3);
lua_setmetatable(L, -2);
lua_setglobal(L, methodsGlobal);
}
int Method::__gc(lua_State* L)

Datei anzeigen

@ -0,0 +1,463 @@
/**
* server/src/lua/persistent.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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 "persistentvariables.hpp"
#include <vector>
#include "enums.hpp"
#include "error.hpp"
#include "event.hpp"
#include "method.hpp"
#include "object.hpp"
#include "sandbox.hpp"
#include "script.hpp"
#include "sets.hpp"
#include "test.hpp"
#include "vectorproperty.hpp"
#include "../core/abstractmethod.hpp"
#include "../utils/contains.hpp"
#include "../world/world.hpp"
namespace Lua {
static void checkTableRecursion(lua_State* L, std::vector<int>& indices)
{
const int index = indices.back();
assert(lua_istable(L, index));
lua_pushnil(L);
while(lua_next(L, index))
{
if(lua_istable(L, -1))
{
if(std::find_if(indices.begin(), indices.end(),
[L](int idx)
{
return lua_rawequal(L, idx, -1);
}) != indices.end())
{
errorTableContainsRecursion(L);
}
indices.push_back(lua_gettop(L));
checkTableRecursion(L, indices);
indices.pop_back();
}
lua_pop(L, 1);
}
}
static void checkTableRecursion(lua_State* L, int index)
{
assert(lua_istable(L, index));
std::vector<int> indices;
indices.push_back(lua_absindex(L, index));
checkTableRecursion(L, indices);
}
static const char* metaTableName = "pv";
struct PersistentVariablesData
{
int registryIndex;
};
void PersistentVariables::registerType(lua_State* L)
{
luaL_newmetatable(L, metaTableName);
lua_pushcfunction(L, __index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, __newindex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, __pairs);
lua_setfield(L, -2, "__pairs");
lua_pushcfunction(L, __len);
lua_setfield(L, -2, "__len");
lua_pushcfunction(L, __gc);
lua_setfield(L, -2, "__gc");
lua_pop(L, 1);
}
bool PersistentVariables::test(lua_State* L, int index)
{
return luaL_testudata(L, index, metaTableName);
}
void PersistentVariables::push(lua_State* L)
{
auto* pv = static_cast<PersistentVariablesData*>(lua_newuserdatauv(L, sizeof(PersistentVariablesData), 1));
luaL_getmetatable(L, metaTableName);
lua_setmetatable(L, -2);
lua_newtable(L);
pv->registryIndex = luaL_ref(L, LUA_REGISTRYINDEX);
}
void PersistentVariables::push(lua_State* L, const nlohmann::json& value)
{
switch(value.type())
{
case nlohmann::json::value_t::null:
return lua_pushnil(L);
case nlohmann::json::value_t::boolean:
return lua_pushboolean(L, static_cast<bool>(value));
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
return lua_pushinteger(L, value);
case nlohmann::json::value_t::number_float:
return lua_pushnumber(L, value);
case nlohmann::json::value_t::string:
{
const std::string s = value;
lua_pushlstring(L, s.data(), s.size());
return;
}
case nlohmann::json::value_t::object:
{
if(value.contains("type"))
{
const std::string type = value["type"];
if(type == "object")
{
if(value.contains("id"))
{
const std::string id = value["id"];
if(auto object = Sandbox::getStateData(L).script().world().getObjectByPath(id))
{
return Object::push(L, object);
}
return lua_pushnil(L);
}
}
else if(type == "vector_property")
{
if(value.contains("object_id") && value.contains("name"))
{
const std::string id = value["object_id"];
if(auto object = Sandbox::getStateData(L).script().world().getObjectByPath(id))
{
const std::string name = value["name"];
if(auto* property = object->getVectorProperty(name); property && property->isScriptReadable())
{
return VectorProperty::push(L, *property);
}
return lua_pushnil(L);
}
}
}
else if(type == "method")
{
if(value.contains("object_id") && value.contains("name"))
{
const std::string id = value["object_id"];
if(auto object = Sandbox::getStateData(L).script().world().getObjectByPath(id))
{
const std::string name = value["name"];
if(auto* method = object->getMethod(name); method && method->isScriptCallable())
{
return Method::push(L, *method);
}
return lua_pushnil(L);
}
}
}
else if(type == "event")
{
if(value.contains("object_id") && value.contains("name"))
{
const std::string id = value["object_id"];
if(auto object = Sandbox::getStateData(L).script().world().getObjectByPath(id))
{
const std::string name = value["name"];
if(auto* event = object->getEvent(name); event && event->isScriptable())
{
return Event::push(L, *event);
}
return lua_pushnil(L);
}
}
}
else if(type == "pv")
{
push(L);
if(value.contains("items"))
{
for(auto item : value["items"])
{
if(item.is_object()) /*[[likely]]*/
{
push(L, item["key"]);
push(L, item["value"]);
lua_settable(L, -3);
}
}
}
return;
}
else if(startsWith(type, "enum."))
{
if(value.contains("value"))
{
return pushEnum(L, type.substr(5).c_str(), value["value"]);
}
}
else if(startsWith(type, "set."))
{
if(value.contains("value"))
{
return pushSet(L, type.substr(4).c_str(), value["value"]);
}
}
}
break;
}
case nlohmann::json::value_t::array:
assert(false);
case nlohmann::json::value_t::binary:
case nlohmann::json::value_t::discarded:
break;
}
assert(false);
errorInternal(L);
}
nlohmann::json PersistentVariables::toJSON(lua_State* L, int index)
{
switch(lua_type(L, index))
{
case LUA_TNIL: /*[[unlikely]]*/
return nullptr;
case LUA_TBOOLEAN:
return (lua_toboolean(L, index) != 0);
case LUA_TNUMBER:
if(lua_isinteger(L, index))
{
return lua_tointeger(L, index);
}
return lua_tonumber(L, index);
case LUA_TSTRING:
return lua_tostring(L, index);
case LUA_TUSERDATA:
if(auto object = Lua::test<::Object>(L, index))
{
auto value = nlohmann::json::object();
value.emplace("type", "object");
value.emplace("id", object->getObjectId());
return value;
}
else if(auto* vectorProperty = VectorProperty::test(L, index))
{
auto value = nlohmann::json::object();
value.emplace("type", "vector_property");
value.emplace("object_id", vectorProperty->object().getObjectId());
value.emplace("name", vectorProperty->name());
return value;
}
else if(auto* method = Method::test(L, index))
{
auto value = nlohmann::json::object();
value.emplace("type", "method");
value.emplace("object_id", method->object().getObjectId());
value.emplace("name", method->name());
return value;
}
else if(auto* event = Event::test(L, index))
{
auto value = nlohmann::json::object();
value.emplace("type", "event");
value.emplace("object_id", event->object().getObjectId());
value.emplace("name", event->name());
return value;
}
else if(test(L, index))
{
auto items = nlohmann::json::array();
auto& pv = *static_cast<PersistentVariablesData*>(luaL_checkudata(L, index, metaTableName));
lua_rawgeti(L, LUA_REGISTRYINDEX, pv.registryIndex);
assert(lua_istable(L, -1));
lua_pushnil(L);
while(lua_next(L, -2))
{
auto item = nlohmann::json::object();
item["key"] = toJSON(L, -2);
item["value"] = toJSON(L, -1);
items.push_back(item);
lua_pop(L, 1); // pop value
}
lua_pop(L, 1); // pop table
auto value = nlohmann::json::object();
value.emplace("type", "pv");
value.emplace("items", items);
return value;
}
else if(lua_getmetatable(L, index))
{
lua_getfield(L, -1, "__name");
std::string_view name = lua_tostring(L, -1);
lua_pop(L, 2);
if(contains(Enums::metaTableNames, name))
{
auto value = nlohmann::json::object();
value.emplace("type", std::string("enum.").append(name));
value.emplace("value", checkEnum(L, index, name.data()));
return value;
}
else if(contains(Sets::metaTableNames, name))
{
auto value = nlohmann::json::object();
value.emplace("type", std::string("set.").append(name));
value.emplace("value", checkSet(L, index, name.data()));
return value;
}
}
break;
case LUA_TTABLE:
case LUA_TLIGHTUSERDATA:
case LUA_TFUNCTION:
case LUA_TTHREAD:
default:
break;
}
assert(false);
errorInternal(L);
}
int PersistentVariables::__index(lua_State* L)
{
const auto& pv = *static_cast<const PersistentVariablesData*>(lua_touserdata(L, 1));
lua_rawgeti(L, LUA_REGISTRYINDEX, pv.registryIndex);
lua_insert(L, 2); // moves key to 3
lua_rawget(L, 2);
return 1;
}
int PersistentVariables::__newindex(lua_State* L)
{
checkValue(L, 2);
if(lua_istable(L, 3))
{
checkTableRecursion(L, 3);
push(L); // push pv userdata
lua_insert(L, -2); // swap pv and table
lua_pushnil(L);
while(lua_next(L, -2))
{
lua_pushvalue(L, -2); // copy key on stack
lua_insert(L, -2); // swap copied key and value
lua_settable(L, -5); // pops copied key and value
}
lua_pop(L, 1); // pop table
}
else if(!test(L, 3))
{
checkValue(L, 3);
}
const auto& pv = *static_cast<const PersistentVariablesData*>(lua_touserdata(L, 1));
lua_rawgeti(L, LUA_REGISTRYINDEX, pv.registryIndex);
lua_insert(L, 2); // moves key to 3 and value to 4
lua_rawset(L, 2);
return 0;
}
int PersistentVariables::__pairs(lua_State* L)
{
lua_getglobal(L, "next");
assert(lua_isfunction(L, -1));
const auto& pv = *static_cast<const PersistentVariablesData*>(lua_touserdata(L, 1));
lua_rawgeti(L, LUA_REGISTRYINDEX, pv.registryIndex);
assert(lua_istable(L, -1));
return 2;
}
int PersistentVariables::__len(lua_State* L)
{
const auto& pv = *static_cast<const PersistentVariablesData*>(lua_touserdata(L, 1));
lua_rawgeti(L, LUA_REGISTRYINDEX, pv.registryIndex);
lua_len(L, -1);
lua_insert(L, -2); // swap length and table
lua_pop(L, 1); // pop table
return 1;
}
int PersistentVariables::__gc(lua_State* L)
{
auto* pv = static_cast<PersistentVariablesData*>(lua_touserdata(L, 1));
luaL_unref(L, LUA_REGISTRYINDEX, pv->registryIndex);
pv->~PersistentVariablesData();
return 0;
}
void PersistentVariables::checkValue(lua_State* L, int index)
{
switch(lua_type(L, index))
{
case LUA_TNIL:
case LUA_TBOOLEAN:
case LUA_TNUMBER:
case LUA_TSTRING:
return; // supported
case LUA_TUSERDATA:
if(Lua::test<::Object>(L, index) || VectorProperty::test(L, index) || Method::test(L, index) || Event::test(L, index))
{
return; // supported
}
else if(lua_getmetatable(L, index))
{
lua_getfield(L, -1, "__name");
std::string_view name = lua_tostring(L, -1);
lua_pop(L, 2);
if(contains(Enums::metaTableNames, name) || contains(Sets::metaTableNames, name))
{
return; // supported
}
}
break;
case LUA_TTABLE:
case LUA_TLIGHTUSERDATA:
case LUA_TFUNCTION:
case LUA_TTHREAD:
default:
break;
}
errorCantStoreValueAsPersistentVariableUnsupportedType(L);
}
}

Datei anzeigen

@ -0,0 +1,52 @@
/**
* server/src/lua/persistentvariables.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_PERSISTENTVARIABLES_HPP
#define TRAINTASTIC_SERVER_LUA_PERSISTENTVARIABLES_HPP
#include <lua.hpp>
#include <nlohmann/json.hpp>
namespace Lua {
class PersistentVariables
{
private:
static int __index(lua_State* L);
static int __newindex(lua_State* L);
static int __pairs(lua_State* L);
static int __len(lua_State* L);
static int __gc(lua_State* L);
static void checkValue(lua_State* L, int index);
public:
static void registerType(lua_State* L);
static bool test(lua_State* L, int index);
static void push(lua_State* L);
static void push(lua_State* L, const nlohmann::json& value);
static nlohmann::json toJSON(lua_State* L, int index);
};
}
#endif

Datei anzeigen

@ -26,6 +26,7 @@
#include "event.hpp"
#include "eventhandler.hpp"
#include "log.hpp"
#include "persistentvariables.hpp"
#include "class.hpp"
#include "to.hpp"
#include "type.hpp"
@ -43,7 +44,7 @@
#define LUA_SANDBOX "_sandbox"
#define LUA_SANDBOX_GLOBALS "_sandbox_globals"
constexpr std::array<std::string_view, 23> readOnlyGlobals = {{
constexpr std::array<std::string_view, 24> readOnlyGlobals = {{
// Lua baselib:
"assert",
"type",
@ -67,6 +68,7 @@ constexpr std::array<std::string_view, 23> readOnlyGlobals = {{
// Objects:
"world",
"log",
"pv",
// Functions:
"is_instance",
// Type info:
@ -127,6 +129,7 @@ namespace Lua {
void Sandbox::close(lua_State* L)
{
syncPersistentVariables(L);
delete *static_cast<StateData**>(lua_getextraspace(L)); // free state data
lua_close(L);
}
@ -162,6 +165,7 @@ SandboxPtr Sandbox::create(Script& script)
*static_cast<StateData**>(lua_getextraspace(L)) = new StateData(script);
// register types:
PersistentVariables::registerType(L);
Enums::registerTypes<LUA_ENUMS>(L);
Sets::registerTypes<LUA_SETS>(L);
Object::registerTypes(L);
@ -226,6 +230,17 @@ SandboxPtr Sandbox::create(Script& script)
Log::push(L);
lua_setfield(L, -2, "log");
// add persistent variables:
if(script.m_persistentVariables.empty())
{
PersistentVariables::push(L);
}
else
{
PersistentVariables::push(L, script.m_persistentVariables);
}
lua_setfield(L, -2, "pv");
// add class types:
lua_newtable(L);
Class::registerValues(L);
@ -268,6 +283,13 @@ int Sandbox::getGlobal(lua_State* L, const char* name)
return type;
}
void Sandbox::syncPersistentVariables(lua_State* L)
{
getGlobal(L, "pv");
getStateData(L).script().m_persistentVariables = PersistentVariables::toJSON(L, -1);
lua_pop(L, 1);
}
int Sandbox::pcall(lua_State* L, int nargs, int nresults, int errfunc)
{
// check if the function has _ENV as first upvalue

Datei anzeigen

@ -130,6 +130,7 @@ class Sandbox
static StateData& getStateData(lua_State* L);
static int getGlobal(lua_State* L, const char* name);
static int pcall(lua_State* L, int nargs = 0, int nresults = 0, int errfunc = 0);
static void syncPersistentVariables(lua_State* L);
};
}

Datei anzeigen

@ -25,8 +25,8 @@
#include "scriptlisttablemodel.hpp"
#include "push.hpp"
#include "../world/world.hpp"
#include "../enum/worldevent.hpp"
#include "../set/worldstate.hpp"
#include <traintastic/enum/worldevent.hpp>
#include <traintastic/set/worldstate.hpp>
#include "../core/attributes.hpp"
#include "../core/method.tpp"
#include "../core/objectproperty.tpp"
@ -65,6 +65,13 @@ Script::Script(World& world, std::string_view _id) :
if(state == LuaScriptState::Running)
stopSandbox();
}}
, clearPersistentVariables{*this, "clear_persistent_variables",
[this]()
{
m_persistentVariables = nullptr;
Log::log(*this, LogMessage::I9003_CLEARED_PERSISTENT_VARIABLES);
updateEnabled();
}}
{
Attributes::addDisplayName(name, DisplayName::Object::name);
Attributes::addEnabled(name, false);
@ -80,6 +87,8 @@ Script::Script(World& world, std::string_view _id) :
m_interfaceItems.add(start);
Attributes::addEnabled(stop, false);
m_interfaceItems.add(stop);
Attributes::addEnabled(clearPersistentVariables, false);
m_interfaceItems.add(clearPersistentVariables);
updateEnabled();
}
@ -92,6 +101,11 @@ void Script::load(WorldLoader& loader, const nlohmann::json& data)
std::string s;
if(loader.readFile(std::filesystem::path(scripts) / m_basename += dotLua, s))
code.loadJSON(s);
if(const auto stateData = loader.getState(id); stateData.contains("persistent_variables"))
{
m_persistentVariables = stateData["persistent_variables"];
}
}
void Script::save(WorldSaver& saver, nlohmann::json& data, nlohmann::json& stateData) const
@ -103,6 +117,15 @@ void Script::save(WorldSaver& saver, nlohmann::json& data, nlohmann::json& state
m_basename = id;
saver.writeFile(std::filesystem::path(scripts) / m_basename += dotLua, code);
if(m_sandbox)
{
Sandbox::syncPersistentVariables(m_sandbox.get());
}
if(!m_persistentVariables.empty())
{
stateData["persistent_variables"] = m_persistentVariables;
}
}
void Script::addToWorld()
@ -156,14 +179,18 @@ void Script::worldEvent(WorldState worldState, WorldEvent worldEvent)
void Script::updateEnabled()
{
const bool editable = contains(m_world.state.value(), WorldState::Edit) && state != LuaScriptState::Running;
const bool stoppedOrError = (state == LuaScriptState::Stopped) || (state == LuaScriptState::Error);
Attributes::setEnabled(id, editable);
Attributes::setEnabled(name, editable);
Attributes::setEnabled(disabled, editable);
Attributes::setEnabled(code, editable);
Attributes::setEnabled(start, state == LuaScriptState::Stopped || state == LuaScriptState::Error);
Attributes::setEnabled(start, stoppedOrError);
Attributes::setEnabled(stop, state == LuaScriptState::Running);
Attributes::setEnabled(clearPersistentVariables, stoppedOrError && !m_persistentVariables.empty());
m_world.luaScripts->updateEnabled();
}
void Script::setState(LuaScriptState value)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -32,11 +32,14 @@ namespace Lua {
class Script : public IdObject
{
friend class Sandbox;
private:
mutable std::string m_basename; //!< filename on disk for script
protected:
SandboxPtr m_sandbox;
nlohmann::json m_persistentVariables;
void load(WorldLoader& loader, const nlohmann::json& data) final;
void save(WorldSaver& saver, nlohmann::json& data, nlohmann::json& stateData) const final;
@ -65,6 +68,7 @@ class Script : public IdObject
Property<std::string> error;
::Method<void()> start;
::Method<void()> stop;
::Method<void()> clearPersistentVariables;
};
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -54,6 +54,17 @@ ScriptList::ScriptList(Object& _parent, std::string_view parentPropertyName)
if(!script->disabled)
script->stop();
}}
, clearPersistentVariables{*this, "clear_persistent_variables",
[this]()
{
for(const auto& script : m_items)
{
if(Attributes::getEnabled(script->clearPersistentVariables))
{
script->clearPersistentVariables();
}
}
}}
{
status.setValueInternal(std::make_shared<LuaStatus>(*this, status.name()));
@ -74,6 +85,9 @@ ScriptList::ScriptList(Object& _parent, std::string_view parentPropertyName)
Attributes::addEnabled(stopAll, false);
m_interfaceItems.add(stopAll);
Attributes::addEnabled(clearPersistentVariables, false);
m_interfaceItems.add(clearPersistentVariables);
}
ScriptList::~ScriptList()
@ -100,20 +114,18 @@ void ScriptList::objectAdded(const std::shared_ptr<Script>& /*object*/)
{
if(m_items.size() == 1)
{
Attributes::setEnabled(startAll, true);
Attributes::setEnabled(stopAll, true);
getWorld(parent()).statuses.appendInternal(status.value());
}
updateEnabled();
}
void ScriptList::objectRemoved(const std::shared_ptr<Script>& /*object*/)
{
if(empty())
{
Attributes::setEnabled(startAll, false);
Attributes::setEnabled(stopAll, false);
getWorld(parent()).statuses.removeInternal(status.value());
}
updateEnabled();
}
bool ScriptList::isListedProperty(std::string_view name)
@ -121,4 +133,22 @@ bool ScriptList::isListedProperty(std::string_view name)
return ScriptListTableModel::isListedProperty(name);
}
void ScriptList::updateEnabled()
{
bool canStart = false;
bool canStop = false;
bool canClearPersistentVariables = false;
for(const auto& script : m_items)
{
canStart |= Attributes::getEnabled(script->start);
canStop |= Attributes::getEnabled(script->stop);
canClearPersistentVariables |= Attributes::getEnabled(script->clearPersistentVariables);
}
Attributes::setEnabled(startAll, canStart);
Attributes::setEnabled(stopAll, canStop);
Attributes::setEnabled(clearPersistentVariables, canClearPersistentVariables);
}
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -47,11 +47,14 @@ class ScriptList final : public ObjectList<Script>
::Method<void(const std::shared_ptr<Script>&)> delete_;
::Method<void()> startAll;
::Method<void()> stopAll;
::Method<void()> clearPersistentVariables;
ScriptList(Object& _parent, std::string_view parentPropertyName);
~ScriptList() final;
TableModelPtr getModel() final;
void updateEnabled();
};
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2022,2024 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,7 +24,9 @@
#define TRAINTASTIC_SERVER_LUA_SETS_HPP
#include "set.hpp"
#include "../../src/set/worldstate.hpp"
#include <array>
#include <string_view>
#include <traintastic/set/worldstate.hpp>
#define LUA_SETS \
WorldState
@ -48,6 +50,14 @@ struct Sets
if constexpr(sizeof...(Ts) != 0)
registerValues<Ts...>(L);
}
template<class... Ts>
inline static const std::array<std::string_view, sizeof...(Ts)> getMetaTableNames()
{
return std::array<std::string_view, sizeof...(Ts)>{set_name_v<Ts>...};
}
inline static const auto metaTableNames = getMetaTableNames<LUA_SETS>();
};
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 Reinder Feenstra
* Copyright (C) 2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -29,6 +29,8 @@
namespace Lua {
constexpr char const* vectorPropertiesGlobal = "vector_properties";
struct VectorPropertyData
{
ObjectPtrWeak object;
@ -63,9 +65,18 @@ AbstractVectorProperty* VectorProperty::test(lua_State* L, int index)
void VectorProperty::push(lua_State* L, AbstractVectorProperty& value)
{
new(lua_newuserdata(L, sizeof(VectorPropertyData))) VectorPropertyData(value);
luaL_getmetatable(L, metaTableName);
lua_setmetatable(L, -2);
lua_getglobal(L, vectorPropertiesGlobal);
lua_rawgetp(L, -1, &value);
if(lua_isnil(L, -1)) // vector property not in table
{
lua_pop(L, 1); // remove nil
new(lua_newuserdata(L, sizeof(VectorPropertyData))) VectorPropertyData(value);
luaL_setmetatable(L, metaTableName);
lua_pushvalue(L, -1); // copy userdata on stack
lua_rawsetp(L, -3, &value); // add vector property to table
}
lua_insert(L, lua_gettop(L) - 1); // swap table and userdata
lua_pop(L, 1); // remove table
}
void VectorProperty::registerType(lua_State* L)
@ -78,6 +89,15 @@ void VectorProperty::registerType(lua_State* L)
lua_pushcfunction(L, __gc);
lua_setfield(L, -2, "__gc");
lua_pop(L, 1);
// weak table for vector property userdata:
lua_newtable(L);
lua_newtable(L); // metatable
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "v");
lua_rawset(L, -3);
lua_setmetatable(L, -2);
lua_setglobal(L, vectorPropertiesGlobal);
}
int VectorProperty::__index(lua_State* L)

Datei anzeigen

@ -1,9 +1,9 @@
/**
* server/src/enum/turnoutposition.hpp
* server/src/os/linux/isserialdevice.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2021 Reinder Feenstra
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -20,9 +20,20 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ENUM_TURNOUTPOSITION_HPP
#define TRAINTASTIC_SERVER_ENUM_TURNOUTPOSITION_HPP
#ifndef TRAINTASTIC_SERVER_OS_LINUX_ISSERIALDEVICE_HPP
#define TRAINTASTIC_SERVER_OS_LINUX_ISSERIALDEVICE_HPP
#include <traintastic/enum/turnoutposition.hpp>
#include "../../utils/startswith.hpp"
namespace Linux {
inline bool isSerialDevice(std::string_view devPath)
{
return startsWith(devPath, "/dev/ttyS") ||
startsWith(devPath, "/dev/ttyUSB") ||
startsWith(devPath, "/dev/ttyACM");
}
}
#endif

Datei anzeigen

@ -0,0 +1,159 @@
/**
* server/src/os/linux/serialportlistimplinotify.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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 "serialportlistimplinotify.hpp"
#include "isserialdevice.hpp"
#include <sys/inotify.h>
#include <poll.h>
#include <filesystem>
#include "../../core/eventloop.hpp"
#include "../../utils/startswith.hpp"
#include "../../utils/setthreadname.hpp"
namespace Linux {
SerialPortListImplInotify::SerialPortListImplInotify(SerialPortList& list)
: SerialPortListImpl(list)
, m_stopEvent{eventfd(0, O_NONBLOCK)}
, m_thread{
[this]()
{
setThreadName("serialport-inotify");
int inotifyFd = inotify_init1(IN_NONBLOCK);
if(inotifyFd == -1)
{
return;
}
int watchFd = inotify_add_watch(inotifyFd, "/dev", IN_CREATE | IN_DELETE);
if(watchFd == -1)
{
close(inotifyFd);
return;
}
// Polling for events:
pollfd fds[2];
fds[0].fd = inotifyFd;
fds[0].events = POLLIN;
fds[1].fd = m_stopEvent;
fds[1].events = POLLIN;
for(;;)
{
int r = poll(fds, 2, -1); // wait for events
if(r == -1)
{
if(errno == EINTR)
{
continue; // interrupted by signal
}
break; // poll failed
}
if(fds[1].revents & POLLIN) // stop event
{
break;
}
else if(fds[0].revents & POLLIN) // inotify event
{
handleInotifyEvents(inotifyFd);
}
}
// clean up:
inotify_rm_watch(inotifyFd, watchFd);
close(inotifyFd);
}}
{
}
SerialPortListImplInotify::~SerialPortListImplInotify()
{
eventfd_write(m_stopEvent, 1);
m_thread.join();
close(m_stopEvent);
}
void SerialPortListImplInotify::handleInotifyEvents(int inotifyFd)
{
char buffer[1024];
ssize_t length = read(inotifyFd, buffer, sizeof(buffer));
if(length < 0)
{
return;
}
for(ssize_t i = 0; i < length;)
{
const auto* event = reinterpret_cast<const inotify_event*>(&buffer[i]);
const auto devPath = std::string("/dev/") + event->name;
if(event->mask & IN_CREATE)
{
if(isSerialDevice(devPath))
{
EventLoop::call(
[this, devPath]()
{
addToList(devPath);
});
}
}
else if(event->mask & IN_DELETE)
{
if(isSerialDevice(devPath))
{
EventLoop::call(
[this, devPath]()
{
removeFromList(devPath);
});
}
}
i += sizeof(inotify_event) + event->len;
}
}
std::vector<std::string> SerialPortListImplInotify::get() const
{
std::vector<std::string> devices;
const std::filesystem::path devDir("/dev");
if(std::filesystem::exists(devDir) && std::filesystem::is_directory(devDir))
{
for(const auto& entry : std::filesystem::directory_iterator(devDir))
{
const auto& devPath = entry.path().string();
if(isSerialDevice(devPath))
{
devices.push_back(devPath);
}
}
}
return devices;
}
}

Datei anzeigen

@ -0,0 +1,48 @@
/**
* server/src/os/linux/serialportlistimplinotify.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_OS_LINUX_SERIALPORTLISTIMPLINOTIFY_HPP
#define TRAINTASTIC_SERVER_OS_LINUX_SERIALPORTLISTIMPLINOTIFY_HPP
#include "../serialportlistimpl.hpp"
#include <thread>
namespace Linux {
class SerialPortListImplInotify final : public SerialPortListImpl
{
private:
int m_stopEvent;
std::thread m_thread;
void handleInotifyEvents(int inotifyFd);
public:
SerialPortListImplInotify(SerialPortList& list);
~SerialPortListImplInotify() final;
std::vector<std::string> get() const final;
};
}
#endif

Datei anzeigen

@ -21,6 +21,7 @@
*/
#include "serialportlistimplsystemd.hpp"
#include "isserialdevice.hpp"
#include "../../core/eventloop.hpp"
#include "../../utils/startswith.hpp"
#include "../../utils/setthreadname.hpp"
@ -42,11 +43,7 @@ static std::string_view getDevPath(sd_device* device)
static bool isSerialDevice(sd_device* device)
{
auto devPath = getDevPath(device);
return
startsWith(devPath, "/dev/ttyS") ||
startsWith(devPath, "/dev/ttyUSB") ||
startsWith(devPath, "/dev/ttyACM");
return isSerialDevice(getDevPath(device));
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2022,2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -26,6 +26,8 @@
#endif
#ifdef HAS_LIBSYSTEMD
#include "linux/serialportlistimplsystemd.hpp"
#elif defined(__linux__)
#include "linux/serialportlistimplinotify.hpp"
#elif defined(WIN32)
#include "windows/serialportlistimplwin32.hpp"
#else

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2022,2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -33,6 +33,10 @@ class SerialPortListImpl;
namespace Linux {
class SerialPortListImplSystemD;
}
#elif defined(__linux__)
namespace Linux {
class SerialPortListImplInotify;
}
#elif defined(WIN32)
namespace Windows {
class SerialPortListImplWin32;
@ -46,6 +50,8 @@ class SerialPortList
private:
#ifdef HAS_LIBSYSTEMD
using Impl = Linux::SerialPortListImplSystemD;
#elif defined(__linux__)
using Impl = Linux::SerialPortListImplInotify;
#elif defined(WIN32)
using Impl = Windows::SerialPortListImplWin32;
#else

Datei anzeigen

@ -1,9 +1,9 @@
/**
* server/src/enum/worldevent.hpp
* server/src/status/simulationstatus.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 Reinder Feenstra
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -20,9 +20,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ENUM_WORLDEVENT_HPP
#define TRAINTASTIC_SERVER_ENUM_WORLDEVENT_HPP
#include "simulationstatus.hpp"
#include <traintastic/enum/worldevent.hpp>
#endif
SimulationStatus::SimulationStatus(Object& parent_, std::string_view parentPropertyName_)
: Status(parent_, parentPropertyName_)
, enabled{this, "enabled", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
{
label.setValueInternal("$world:simulation$");
}

Datei anzeigen

@ -1,9 +1,9 @@
/**
* server/src/set/worldstate.hpp
* server/src/status/simulationstatus.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2022 Reinder Feenstra
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -20,9 +20,19 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_SET_WORLDSTATE_HPP
#define TRAINTASTIC_SERVER_SET_WORLDSTATE_HPP
#ifndef TRAINTASTIC_SERVER_STATUS_SIMULATIONSTATUS_HPP
#define TRAINTASTIC_SERVER_STATUS_SIMULATIONSTATUS_HPP
#include <traintastic/set/worldstate.hpp>
#include "status.hpp"
class SimulationStatus : public Status
{
CLASS_ID("status.simulation")
public:
Property<bool> enabled;
SimulationStatus(Object& parent_, std::string_view parentPropertyName_);
};
#endif

Datei anzeigen

@ -147,7 +147,7 @@ Train::Train(World& world, std::string_view _id) :
mode{this, "mode", TrainMode::ManualUnprotected, PropertyFlags::ReadWrite | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, mute{this, "mute", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, noSmoke{this, "no_smoke", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, blocks{*this, "blocks", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState},
, blocks{*this, "blocks", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly},
zones{*this, "zones", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly},
notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, onBlockAssigned{*this, "on_block_assigned", EventFlags::Scriptable}

Datei anzeigen

@ -1,28 +0,0 @@
/**
* server/src/utils/attributes.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_UTILS_ATTRIBUTES_HPP
#define TRAINTASTIC_SERVER_UTILS_ATTRIBUTES_HPP
#endif // ATTRIBUTES_HPP

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2022,2024 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,8 +23,15 @@
#ifndef TRAINTASTIC_SERVER_UTILS_CONTAINS_HPP
#define TRAINTASTIC_SERVER_UTILS_CONTAINS_HPP
#include <array>
#include <vector>
template<class T, std::size_t N>
inline bool contains(const std::array<T, N>& array, T value)
{
return std::find(array.begin(), array.end(), value) != array.end();
}
template<class T>
inline bool contains(const std::vector<T>& vector, T value)
{

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -54,7 +54,7 @@ RailVehicleList::RailVehicleList(Object& _parent, std::string_view parentPropert
Attributes::addDisplayName(create, DisplayName::List::create);
Attributes::addEnabled(create, editable);
Attributes::addClassList(create, RailVehicles::classList);
Attributes::addClassList(create, RailVehicles::classList());
m_interfaceItems.add(create);
Attributes::addDisplayName(delete_, DisplayName::List::delete_);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023 Reinder Feenstra
* Copyright (C) 2019-2020,2023-2024 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,6 +22,23 @@
#include "railvehicles.hpp"
#include "../../utils/ifclassidcreate.hpp"
#include "../../utils/makearray.hpp"
#include "locomotive.hpp"
#include "multipleunit.hpp"
#include "freightwagon.hpp"
#include "tankwagon.hpp"
tcb::span<const std::string_view> RailVehicles::classList()
{
static constexpr auto classes = makeArray(
Locomotive::classId,
MultipleUnit::classId,
FreightWagon::classId,
TankWagon::classId
);
return classes;
}
std::shared_ptr<RailVehicle> RailVehicles::create(World& world, std::string_view classId, std::string_view id)
{

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023 Reinder Feenstra
* Copyright (C) 2019-2020,2023-2024 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,25 +24,13 @@
#define TRAINTASTIC_SERVER_VEHICLE_RAIL_RAILVEHICLES_HPP
#include "railvehicle.hpp"
#include "../../utils/makearray.hpp"
#include "locomotive.hpp"
#include "multipleunit.hpp"
#include "freightwagon.hpp"
#include "tankwagon.hpp"
struct RailVehicles
{
static constexpr std::string_view classIdPrefix = "vehicle.rail.";
static constexpr auto classList = makeArray(
Locomotive::classId,
MultipleUnit::classId,
FreightWagon::classId,
TankWagon::classId
);
static tcb::span<const std::string_view> classList();
static std::shared_ptr<RailVehicle> create(World& world, std::string_view classId, std::string_view id);
};
#endif
#endif

Datei anzeigen

@ -71,6 +71,7 @@
#include "../train/trainlist.hpp"
#include "../vehicle/rail/railvehiclelist.hpp"
#include "../lua/scriptlist.hpp"
#include "../status/simulationstatus.hpp"
#include "../utils/category.hpp"
using nlohmann::json;
@ -127,6 +128,8 @@ void World::init(World& world)
world.blockRailTiles.setValueInternal(std::make_shared<BlockRailTileList>(world, world.blockRailTiles.name()));
world.linkRailTiles.setValueInternal(std::make_shared<LinkRailTileList>(world, world.linkRailTiles.name()));
world.nxManager.setValueInternal(std::make_shared<NXManager>(world, world.nxManager.name()));
world.simulationStatus.setValueInternal(std::make_shared<SimulationStatus>(world, world.simulationStatus.name()));
}
World::World(Private /*unused*/) :
@ -235,8 +238,18 @@ World::World(Private /*unused*/) :
simulation{this, "simulation", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
[this](bool value)
{
simulationStatus->enabled.setValueInternal(value);
if(value)
{
statuses.appendInternal(simulationStatus.value());
}
else
{
statuses.removeInternal(simulationStatus.value());
}
event(value ? WorldEvent::SimulationEnabled : WorldEvent::SimulationDisabled);
}},
simulationStatus{this, "simulation_status", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore},
save{*this, "save", MethodFlags::NoScript,
[this]()
{

Datei anzeigen

@ -57,6 +57,7 @@ class NXManager;
class Clock;
class TrainList;
class RailVehicleList;
class SimulationStatus;
template <typename T>
class ControllerList;
@ -146,6 +147,7 @@ class World : public Object
Property<bool> mute;
Property<bool> noSmoke;
Property<bool> simulation;
ObjectProperty<SimulationStatus> simulationStatus;
Method<void()> save;

Datei anzeigen

@ -36,10 +36,12 @@
#include "../board/board.hpp"
#include "../board/tile/tiles.hpp"
#include "../hardware/interface/interfaces.hpp"
#include "../hardware/interface/dccexinterface.hpp" //! \todo Remove in v0.4
#include "../hardware/decoder/decoder.hpp"
#include "../hardware/decoder/decoderfunction.hpp"
#include "../hardware/identification/identification.hpp"
#include "../vehicle/rail/railvehicles.hpp"
#include "../vehicle/rail/freightwagon.hpp" //! \todo Remove in v0.4
#include "../train/train.hpp"
#include "../train/trainblockstatus.hpp"
#include "../lua/script.hpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../src/world/world.hpp"
#include "../src/core/method.tpp"
#include "../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../src/world/world.hpp"
#include "../src/core/method.tpp"
#include "../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../src/world/world.hpp"
#include "../src/core/method.tpp"
#include "../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../../src/world/world.hpp"
#include "../../src/core/method.tpp"
#include "../../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../src/world/world.hpp"
#include "../src/core/method.tpp"
#include "../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../src/world/world.hpp"
#include "../src/world/worldloader.hpp"
#include "../src/world/worldsaver.hpp"

Datei anzeigen

@ -22,7 +22,7 @@
#ifndef __aarch64__
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../src/world/world.hpp"
#include "../src/core/method.tpp"
#include "../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../../src/core/method.tpp"
#include "../../src/core/objectproperty.tpp"
#include "../../src/world/world.hpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../../src/lua/check.hpp"
#include "protect.hpp"
#include <string_view>
@ -28,7 +28,7 @@
TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
{
{
INFO("nil")
INFO("nil");
lua_State* L = newStateWithProtect();
@ -40,7 +40,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("false")
INFO("false");
lua_State* L = newStateWithProtect();
@ -53,7 +53,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("true")
INFO("true");
lua_State* L = newStateWithProtect();
@ -66,7 +66,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("123")
INFO("123");
lua_State* L = newStateWithProtect();
@ -78,7 +78,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("0.5")
INFO("0.5");
lua_State* L = newStateWithProtect();
@ -90,7 +90,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("\"test\"")
INFO("\"test\"");
lua_State* L = newStateWithProtect();
@ -102,7 +102,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("table")
INFO("table");
lua_State* L = newStateWithProtect();
@ -114,7 +114,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("userdata")
INFO("userdata");
lua_State* L = newStateWithProtect();
@ -126,7 +126,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", bool)
}
{
INFO("lightuserdata")
INFO("lightuserdata");
lua_State* L = newStateWithProtect();

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../../src/lua/check.hpp"
#include "protect.hpp"
#include "../../src/lua/enums.hpp"
@ -56,7 +56,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
const TestType lastValue = EnumValues<TestType>::value.rbegin()->first;
{
INFO("nil")
INFO("nil");
lua_State* L = createState<TestType>();
@ -68,7 +68,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("false")
INFO("false");
lua_State* L = createState<TestType>();
@ -80,7 +80,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("true")
INFO("true");
lua_State* L = createState<TestType>();
@ -92,7 +92,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("enum")
INFO("enum");
lua_State* L = createState<TestType>();
@ -105,7 +105,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("other enum")
INFO("other enum");
lua_State* L = createState<TestType>();
@ -117,7 +117,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("123")
INFO("123");
lua_State* L = createState<TestType>();
@ -129,7 +129,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("0.5")
INFO("0.5");
lua_State* L = createState<TestType>();
@ -141,7 +141,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("\"test\"")
INFO("\"test\"");
lua_State* L = createState<TestType>();
@ -153,7 +153,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("table")
INFO("table");
lua_State* L = createState<TestType>();
@ -165,7 +165,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("userdata")
INFO("userdata");
lua_State* L = createState<TestType>();
@ -177,7 +177,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", LUA_ENUMS)
}
{
INFO("lightuserdata")
INFO("lightuserdata");
lua_State* L = createState<TestType>();

Datei anzeigen

@ -20,7 +20,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "protect.hpp"
#include "../../src/lua/check.hpp"
#include <string_view>
@ -28,7 +29,7 @@
TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
{
{
INFO("nil")
INFO("nil");
lua_State* L = newStateWithProtect();
@ -40,7 +41,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
}
{
INFO("false")
INFO("false");
lua_State* L = newStateWithProtect();
@ -52,7 +53,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
}
{
INFO("true")
INFO("true");
lua_State* L = newStateWithProtect();
@ -64,59 +65,59 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
}
{
INFO("123")
INFO("123");
lua_State* L = newStateWithProtect();
lua_pushinteger(L, 123);
TestType r;
REQUIRE(protect<Lua::check<TestType>>(r, L, -1));
REQUIRE(r == Approx(123));
REQUIRE(r == Catch::Approx(123));
closeStateWithProtect(L);
}
{
INFO("0.5")
INFO("0.5");
lua_State* L = newStateWithProtect();
lua_pushnumber(L, 0.5);
TestType r;
REQUIRE(protect<Lua::check<TestType>>(r, L, -1));
REQUIRE(r == Approx(0.5));
REQUIRE(r == Catch::Approx(0.5));
closeStateWithProtect(L);
}
{
INFO("\"123\"")
INFO("\"123\"");
lua_State* L = newStateWithProtect();
lua_pushliteral(L, "123");
TestType r;
REQUIRE(protect<Lua::check<TestType>>(r, L, -1));
REQUIRE(r == Approx(123));
REQUIRE(r == Catch::Approx(123));
closeStateWithProtect(L);
}
{
INFO("\"0.5\"")
INFO("\"0.5\"");
lua_State* L = newStateWithProtect();
lua_pushliteral(L, "0.5");
TestType r;
REQUIRE(protect<Lua::check<TestType>>(r, L, -1));
REQUIRE(r == Approx(0.5));
REQUIRE(r == Catch::Approx(0.5));
closeStateWithProtect(L);
}
{
INFO("\"test\"")
INFO("\"test\"");
lua_State* L = newStateWithProtect();
@ -128,7 +129,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
}
{
INFO("table")
INFO("table");
lua_State* L = newStateWithProtect();
@ -140,7 +141,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
}
{
INFO("userdata")
INFO("userdata");
lua_State* L = newStateWithProtect();
@ -152,7 +153,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", float, double)
}
{
INFO("lightuserdata")
INFO("lightuserdata");
lua_State* L = newStateWithProtect();

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "protect.hpp"
#include "../../src/lua/check.hpp"
#include <string_view>
@ -28,7 +28,7 @@
TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t)
{
{
INFO("nil")
INFO("nil");
lua_State* L = newStateWithProtect();
@ -40,7 +40,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("false")
INFO("false");
lua_State* L = newStateWithProtect();
@ -52,7 +52,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("true")
INFO("true");
lua_State* L = newStateWithProtect();
@ -64,7 +64,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("123")
INFO("123");
lua_State* L = newStateWithProtect();
@ -77,7 +77,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("1000")
INFO("1000");
lua_State* L = newStateWithProtect();
@ -97,7 +97,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("-1000")
INFO("-1000");
lua_State* L = newStateWithProtect();
@ -121,7 +121,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("4.2")
INFO("4.2");
lua_State* L = newStateWithProtect();
@ -133,7 +133,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("\"123\"")
INFO("\"123\"");
lua_State* L = newStateWithProtect();
@ -146,7 +146,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("\"1000\"")
INFO("\"1000\"");
lua_State* L = newStateWithProtect();
@ -166,7 +166,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("\"-1000\"")
INFO("\"-1000\"");
lua_State* L = newStateWithProtect();
@ -190,7 +190,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("\"4.2\"")
INFO("\"4.2\"");
lua_State* L = newStateWithProtect();
@ -202,7 +202,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("\"test\"")
INFO("\"test\"");
lua_State* L = newStateWithProtect();
@ -214,7 +214,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("table")
INFO("table");
lua_State* L = newStateWithProtect();
@ -226,7 +226,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("userdata")
INFO("userdata");
lua_State* L = newStateWithProtect();
@ -238,7 +238,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", int8_t, int16_t, int32_t,
}
{
INFO("lightuserdata")
INFO("lightuserdata");
lua_State* L = newStateWithProtect();

Datei anzeigen

@ -20,14 +20,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "protect.hpp"
#include "../../src/lua/check.hpp"
TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_view)
{
{
INFO("nil")
INFO("nil");
lua_State* L = newStateWithProtect();
@ -39,7 +39,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("false")
INFO("false");
lua_State* L = newStateWithProtect();
@ -51,7 +51,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("true")
INFO("true");
lua_State* L = newStateWithProtect();
@ -63,7 +63,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("123")
INFO("123");
lua_State* L = newStateWithProtect();
@ -77,7 +77,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("0.5")
INFO("0.5");
lua_State* L = newStateWithProtect();
@ -91,7 +91,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("\"test\"")
INFO("\"test\"");
lua_State* L = newStateWithProtect();
@ -104,7 +104,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("table")
INFO("table");
lua_State* L = newStateWithProtect();
@ -116,7 +116,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("userdata")
INFO("userdata");
lua_State* L = newStateWithProtect();
@ -128,7 +128,7 @@ TEMPLATE_TEST_CASE("Lua::check<>", "[lua][lua-check]", std::string, std::string_
}
{
INFO("lightuserdata")
INFO("lightuserdata");
lua_State* L = newStateWithProtect();

Datei anzeigen

@ -20,7 +20,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <algorithm>
#include "protect.hpp"
#include "run.hpp"
#include "../../src/lua/enums.hpp"
@ -48,7 +49,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
const TestType lastKey = EnumValues<TestType>::value.rbegin()->first;
{
INFO("write to enum.*")
INFO("write to enum.*");
lua_State* L = createState<TestType>();
@ -79,7 +80,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("iterate over enum.*")
INFO("iterate over enum.*");
lua_State* L = createState<TestType>();
@ -141,7 +142,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("single value")
INFO("single value");
lua_State* L = createState<TestType>();
@ -160,7 +161,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("tostring")
INFO("tostring");
lua_State* L = createState<TestType>();
@ -182,7 +183,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("add")
INFO("add");
lua_State* L = createState<TestType>();
@ -194,7 +195,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("subtract")
INFO("subtract");
lua_State* L = createState<TestType>();
@ -206,7 +207,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("multiply")
INFO("multiply");
lua_State* L = createState<TestType>();
@ -218,7 +219,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("modulo")
INFO("modulo");
lua_State* L = createState<TestType>();
@ -230,7 +231,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("power")
INFO("power");
lua_State* L = createState<TestType>();
@ -242,7 +243,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("divide")
INFO("divide");
lua_State* L = createState<TestType>();
@ -254,7 +255,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("divide (integer)")
INFO("divide (integer)");
lua_State* L = createState<TestType>();
@ -266,7 +267,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("binary and")
INFO("binary and");
lua_State* L = createState<TestType>();
@ -278,7 +279,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("binary or")
INFO("binary or");
lua_State* L = createState<TestType>();
@ -290,7 +291,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("binary xor")
INFO("binary xor");
lua_State* L = createState<TestType>();
@ -302,7 +303,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("shift left")
INFO("shift left");
lua_State* L = createState<TestType>();
@ -314,7 +315,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("shift right")
INFO("shift right");
lua_State* L = createState<TestType>();
@ -326,7 +327,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("unary minus")
INFO("unary minus");
lua_State* L = createState<TestType>();
@ -337,7 +338,7 @@ TEMPLATE_TEST_CASE("Lua::Enum<>", "[lua][lua-enum]", LUA_ENUMS)
}
{
INFO("binary not")
INFO("binary not");
lua_State* L = createState<TestType>();

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../../src/lua/push.hpp"
// Enums:

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "../../src/world/world.hpp"
#include "../../src/core/method.tpp"
#include "../../src/core/objectproperty.tpp"

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../../hardware/interfaces.hpp"
#include "../../../src/core/method.tpp"
#include "../../../src/core/objectproperty.tpp"

Datei anzeigen

@ -0,0 +1,95 @@
/**
* server/test/lua/script/deadobject.cpp
*
* This file is part of the traintastic test suite.
*
* Copyright (C) 2024 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 <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include "../../../src/core/eventloop.hpp"
#include "../../../src/core/method.tpp"
#include "../../../src/core/objectproperty.tpp"
#include "../../../src/log/log.hpp"
#include "../../../src/log/memorylogger.hpp"
#include "../../../src/lua/scriptlist.hpp"
#include "../../../src/train/train.hpp"
#include "../../../src/train/trainlist.hpp"
#include "../../../src/utils/endswith.hpp"
#include "../../../src/world/world.hpp"
using Catch::Matchers::EndsWith;
TEST_CASE("Lua script: dead object", "[lua][lua-script][lua-script-dead-object]")
{
Log::enableMemoryLogger(100);
EventLoop::threadId = std::this_thread::get_id(); // else MemoryLogger will post it to the event loop
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// create train:
auto train = world->trains->create();
REQUIRE(train);
std::weak_ptr<Train> trainWeak = train;
train->id = "train";
train->name = "Train";
train.reset();
// read name:
script->code =
"local train = world.get_object(\"train\")\n"
"assert(train.name == \"Train\")\n"
"world.on_event(\n"
" function ()\n"
" assert(train.name == \"Train\")\n"
" end)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
// delete train:
world->trains->delete_(trainWeak.lock());
REQUIRE(trainWeak.expired());
// trigger event to call dead object:
world->simulation = true;
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running); // error in event does not stop script
// check log:
REQUIRE(Log::getMemoryLogger());
auto& logger = *Log::getMemoryLogger();
REQUIRE(logger.size() != 0);
auto& lastLog = logger[logger.size() - 1];
REQUIRE(lastLog.message == LogMessage::E9001_X_DURING_EXECUTION_OF_X_EVENT_HANDLER);
REQUIRE(lastLog.args);
auto args = *lastLog.args;
REQUIRE(args.size() == 2);
REQUIRE_THAT(args[0], EndsWith("dead object"));
REQUIRE(args[1] == "world.on_event");
// stop script:
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}

Datei anzeigen

@ -0,0 +1,900 @@
/**
* server/test/lua/script/persistentvariables.cpp
*
* This file is part of the traintastic test suite.
*
* Copyright (C) 2024 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 <catch2/catch_test_macros.hpp>
#include "../../../src/core/method.tpp"
#include "../../../src/core/objectproperty.tpp"
#include "../../../src/lua/scriptlist.hpp"
#include "../../../src/train/train.hpp"
#include "../../../src/train/trainlist.hpp"
#include "../../../src/utils/endswith.hpp"
#include "../../../src/world/world.hpp"
#include "../../../src/world/worldloader.hpp"
#include "../../../src/world/worldsaver.hpp"
TEST_CASE("Lua script: pv - save/restore - bool", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = true";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == true)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - int", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = 42";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == 42)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - float", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = math.pi";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == math.pi)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - string", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = 'traintastic'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == 'traintastic')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - enum", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = enum.world_event.RUN";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == enum.world_event.RUN)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - set", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = set.world_state.RUN";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == set.world_state.RUN)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - object", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = world";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == world)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - vector property", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
auto train = world->trains->create();
REQUIRE(train);
train->id = "train";
// set pv:
script->code = "pv.test = world.get_object('train').blocks";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == world.get_object('train').blocks)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
train.reset();
}
TEST_CASE("Lua script: pv - save/restore - method", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = world.stop";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == world.stop)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - event", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = world.on_event";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test == world.on_event)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - unsupported - function", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = function(a, b)\nreturn a+b\nend";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Error);
REQUIRE(endsWith(script->error.value(), "can't store value as persistent variable, unsupported type"));
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - table, empty", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = {}";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test ~= nil)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - table, array like", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = {1, 2, 3}";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test[1] == 1)\nassert(pv.test[2] == 2)\nassert(pv.test[3] == 3)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - table, array length", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = {1, 2, 3}";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(#pv.test == 3)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - table, map like", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv.test = {one=1, two=2}";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv.test.one == 1)\nassert(pv.test.two == 2)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - bool as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[true] = false";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[true] == false)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - float as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[math.pi] = 3";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[math.pi] == 3)";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - enum key as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[enum.world_event.RUN] = 'y'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[enum.world_event.RUN] == 'y')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - set as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[set.world_state.RUN] = 'test'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[set.world_state.RUN] == 'test')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - object as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[world] = 'world'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[world] == 'world')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - vector property as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
auto train = world->trains->create();
REQUIRE(train);
train->id = "train";
// set pv:
script->code = "pv[world.get_object('train').blocks] = 'test'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[world.get_object('train').blocks] == 'test')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
train.reset();
}
TEST_CASE("Lua script: pv - save/restore - method as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[world.stop] = 'test'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[world.stop] == 'test')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/restore - event as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[world.on_event] = 'test'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code = "assert(pv[world.on_event] == 'test')";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - unsupported - function as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[function(a, b)\nreturn a+b\nend] = 'test'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Error);
REQUIRE(endsWith(script->error.value(), "can't store value as persistent variable, unsupported type"));
script.reset();
}
TEST_CASE("Lua script: pv - unsupported - table as key", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code = "pv[{}] = 'test'";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Error);
REQUIRE(endsWith(script->error.value(), "can't store value as persistent variable, unsupported type"));
script.reset();
}
TEST_CASE("Lua script: pv - unsupported - table recursion", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code =
"t = {}\n"
"t['t'] = t\n"
"pv['t'] = t";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Error);
REQUIRE(endsWith(script->error.value(), "table contains recursion"));
script.reset();
}
TEST_CASE("Lua script: pv - unsupported - table recursion 2", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code =
"t = {}\n"
"a = {}\n"
"b = {}\n"
"t['a'] = a\n"
"t['b'] = b\n"
"t['a']['b'] = b\n"
"t['b']['a'] = a\n"
"pv['t'] = t";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Error);
REQUIRE(endsWith(script->error.value(), "table contains recursion"));
script.reset();
}
TEST_CASE("Lua script: pv - pairs()", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code =
"pv.test = {a=1, b=2, c=3}\n"
"for k, v in pairs(pv.test) do\n"
" assert((k == 'a' and v == 1) or (k == 'b' and v == 2) or (k == 'c' and v == 3))\n"
"end";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code =
"for k, v in pairs(pv.test) do\n"
" assert((k == 'a' and v == 1) or (k == 'b' and v == 2) or (k == 'c' and v == 3))\n"
"end";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - ipairs()", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code =
"pv.test = {5, 4, 3, 2, 1}\n"
"assert(#pv.test == 5)\n"
"local n = 1\n"
"for k, v in ipairs(pv.test) do\n"
" assert(k == n)\n"
" assert(v == (#pv.test - n + 1))\n"
" n = n + 1\n"
"end";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
// check pv:
script->code =
"assert(#pv.test == 5)\n"
"local n = 1\n"
"for k, v in ipairs(pv.test) do\n"
" assert(k == n)\n"
" assert(v == (#pv.test - n + 1))\n"
" n = n + 1\n"
"end";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - clear", "[lua][lua-script][lua-script-pv]")
{
auto world = World::create();
REQUIRE(world);
auto script = world->luaScripts->create();
REQUIRE(script);
// set pv:
script->code =
"pv.object = world\n"
"pv.table = {a=1, b=2, c=3}\n"
"pv.array = {5, 4, 3, 2, 1}\n";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script->clearPersistentVariables();
// check pv:
script->code =
"assert(pv.object == nil)\n"
"assert(pv.table == nil)\n"
"assert(pv.array == nil)\n";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
script.reset();
}
TEST_CASE("Lua script: pv - save/load", "[lua][lua-script][lua-script-pv]")
{
static const std::string code =
"pv.bool = true\n"
"pv.int = 42\n"
"pv.float = 2.5\n"
"pv.string = \"Traintastic\"\n"
"pv.object = world\n"
"pv.method = world.stop\n"
"pv.event = world.on_event\n"
"pv.table = {a=1, b=2, c=3}\n"
"pv.array = {5, 4, 3, 2, 1}\n";
std::filesystem::path ctw;
std::string worldUUID;
{
auto world = World::create();
REQUIRE(world);
worldUUID = world->uuid;
auto script = world->luaScripts->create();
REQUIRE(script);
script->id = "script";
// set pv:
script->code = code;
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
{
ctw = std::filesystem::temp_directory_path() / std::string(world->uuid.value()).append(World::dotCTW);
WorldSaver saver(*world, ctw);
}
}
{
std::shared_ptr<World> world;
{
WorldLoader loader(ctw);
world = loader.world();
REQUIRE(world);
}
{
REQUIRE(world->uuid.value() == worldUUID);
REQUIRE(world->luaScripts->length == 1);
auto script = world->luaScripts->operator[](0);
REQUIRE(script);
REQUIRE(script->id.value() == "script");
REQUIRE(script->code.value() == code);
script->code =
"assert(pv.bool == true)\n"
"assert(pv.int == 42)\n"
"assert(pv.float == 2.5)\n"
"assert(pv.string == \"Traintastic\")\n"
"assert(pv.object == world)\n"
"assert(pv.method == world.stop)\n"
"assert(pv.event == world.on_event)\n"
"assert(pv.table.a == 1)\n"
"assert(pv.table.b == 2)\n"
"assert(pv.table.c == 3)\n"
"assert(#pv.array == 5)\n"
"assert(pv.array[1] == 5)\n"
"assert(pv.array[2] == 4)\n"
"assert(pv.array[3] == 3)\n"
"assert(pv.array[4] == 2)\n"
"assert(pv.array[5] == 1)\n";
script->start();
INFO(script->error.value());
REQUIRE(script->state.value() == LuaScriptState::Running);
script->stop();
REQUIRE(script->state.value() == LuaScriptState::Stopped);
}
}
REQUIRE(std::filesystem::remove(ctw));
}

Datei anzeigen

@ -20,7 +20,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <algorithm>
#include "protect.hpp"
#include "run.hpp"
#include "../../src/lua/sets.hpp"
@ -49,7 +50,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
const TestType lastKey = set_values_v<TestType>.rbegin()->first;
{
INFO("write to set.*")
INFO("write to set.*");
lua_State* L = createState<TestType>();
@ -80,7 +81,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("iterate over enum.*")
INFO("iterate over enum.*");
lua_State* L = createState<TestType>();
@ -142,7 +143,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("single value")
INFO("single value");
lua_State* L = createState<TestType>();
@ -161,7 +162,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("tostring")
INFO("tostring");
lua_State* L = createState<TestType>();
@ -205,7 +206,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("add")
INFO("add");
lua_State* L = createState<TestType>();
@ -221,7 +222,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("subtract")
INFO("subtract");
lua_State* L = createState<TestType>();
@ -237,7 +238,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("multiply")
INFO("multiply");
lua_State* L = createState<TestType>();
@ -249,7 +250,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("modulo")
INFO("modulo");
lua_State* L = createState<TestType>();
@ -261,7 +262,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("power")
INFO("power");
lua_State* L = createState<TestType>();
@ -273,7 +274,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("divide")
INFO("divide");
lua_State* L = createState<TestType>();
@ -285,7 +286,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("divide (integer)")
INFO("divide (integer)");
lua_State* L = createState<TestType>();
@ -297,7 +298,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("binary and")
INFO("binary and");
lua_State* L = createState<TestType>();
@ -313,7 +314,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("binary or")
INFO("binary or");
lua_State* L = createState<TestType>();
@ -329,7 +330,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("binary xor")
INFO("binary xor");
lua_State* L = createState<TestType>();
@ -341,7 +342,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("shift left")
INFO("shift left");
lua_State* L = createState<TestType>();
@ -353,7 +354,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("shift right")
INFO("shift right");
lua_State* L = createState<TestType>();
@ -365,7 +366,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("unary minus")
INFO("unary minus");
lua_State* L = createState<TestType>();
@ -376,7 +377,7 @@ TEMPLATE_TEST_CASE("Lua::Set<>", "[lua][lua-set]", LUA_SETS)
}
{
INFO("binary not")
INFO("binary not");
lua_State* L = createState<TestType>();

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../../src/lua/to.hpp"
#include <string_view>
@ -38,13 +38,13 @@ TEMPLATE_TEST_CASE("Lua::to<>", "[lua][lua-to]", bool)
{
lua_State* L = luaL_newstate();
INFO("nil")
INFO("nil");
lua_pushnil(L);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("false")
INFO("false");
lua_pushboolean(L, false);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
{
@ -54,7 +54,7 @@ TEMPLATE_TEST_CASE("Lua::to<>", "[lua][lua-to]", bool)
}
lua_pop(L, 1);
INFO("true")
INFO("true");
lua_pushboolean(L, true);
REQUIRE(Lua::to<TestType>(L, -1));
{
@ -64,37 +64,37 @@ TEMPLATE_TEST_CASE("Lua::to<>", "[lua][lua-to]", bool)
}
lua_pop(L, 1);
INFO("123")
INFO("123");
lua_pushinteger(L, 123);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("0.5")
INFO("0.5");
lua_pushnumber(L, 0.5);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("\"test\"")
INFO("\"test\"");
lua_pushliteral(L, "test");
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("table")
INFO("table");
lua_newtable(L);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("userdata")
INFO("userdata");
lua_newuserdata(L, 0);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("lightuserdata")
INFO("lightuserdata");
lua_pushlightuserdata(L, nullptr);
REQUIRE_FALSE(Lua::to<TestType>(L, -1));
REQUIRE_TRY_TO_FAIL();

Datei anzeigen

@ -20,7 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include "../../src/lua/enums.hpp"
#include "../../src/lua/to.hpp"
#include <string_view>
@ -57,25 +57,25 @@ TEMPLATE_TEST_CASE("Lua::to<>", "[lua][lua-to]", LUA_ENUMS)
lua_State* L = luaL_newstate();
INFO("nil")
INFO("nil");
lua_pushnil(L);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("false")
INFO("false");
lua_pushboolean(L, false);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("true")
INFO("true");
lua_pushboolean(L, true);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("enum")
INFO("enum");
Lua::Enum<TestType>::registerType(L);
Lua::Enum<TestType>::push(L, lastValue);
REQUIRE(Lua::to<TestType>(L, -1) == lastValue);
@ -86,44 +86,44 @@ TEMPLATE_TEST_CASE("Lua::to<>", "[lua][lua-to]", LUA_ENUMS)
}
lua_pop(L, 1);
INFO("other enum")
INFO("other enum");
Lua::Enum<OtherEnumType>::registerType(L);
Lua::Enum<OtherEnumType>::push(L, EnumValues<OtherEnumType>::value.rbegin()->first);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("123")
INFO("123");
lua_pushinteger(L, 123);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("0.5")
INFO("0.5");
lua_pushnumber(L, 0.5);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("\"test\"")
INFO("\"test\"");
lua_pushliteral(L, "test");
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("table")
INFO("table");
lua_newtable(L);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("userdata")
INFO("userdata");
lua_newuserdata(L, 0);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("lightuserdata")
INFO("lightuserdata");
lua_pushlightuserdata(L, nullptr);
REQUIRE(Lua::to<TestType>(L, -1) == firstValue);
REQUIRE_TRY_TO_FAIL();

Datei anzeigen

@ -20,13 +20,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "../../src/lua/to.hpp"
#include <string_view>
#define REQUIRE_TRY_TO(x) \
{ \
TestType v; \
TestType v = 0.0; \
REQUIRE(Lua::to<TestType>(L, -1, v)); \
REQUIRE(v == x); \
}
@ -43,67 +44,67 @@ TEMPLATE_TEST_CASE("Lua::to<>", "[lua][lua-to]", float, double)
{
lua_State* L = luaL_newstate();
INFO("nil")
INFO("nil");
lua_pushnil(L);
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("false")
INFO("false");
lua_pushboolean(L, false);
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("true")
INFO("true");
lua_pushboolean(L, true);
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("123")
INFO("123");
lua_pushinteger(L, 123);
REQUIRE(Lua::to<TestType>(L, -1) == Approx(123));
REQUIRE_TRY_TO(Approx(123));
REQUIRE(Lua::to<TestType>(L, -1) == Catch::Approx(123));
REQUIRE_TRY_TO(Catch::Approx(123));
lua_pop(L, 1);
INFO("0.5")
INFO("0.5");
lua_pushnumber(L, 0.5);
REQUIRE(Lua::to<TestType>(L, -1) == Approx(0.5));
REQUIRE_TRY_TO(Approx(0.5));
REQUIRE(Lua::to<TestType>(L, -1) == Catch::Approx(0.5));
REQUIRE_TRY_TO(Catch::Approx(0.5));
lua_pop(L, 1);
INFO("\"123\"")
INFO("\"123\"");
lua_pushliteral(L, "123");
REQUIRE(Lua::to<TestType>(L, -1) == Approx(123));
REQUIRE_TRY_TO(Approx(123));
REQUIRE(Lua::to<TestType>(L, -1) == Catch::Approx(123));
REQUIRE_TRY_TO(Catch::Approx(123));
lua_pop(L, 1);
INFO("\"0.5\"")
INFO("\"0.5\"");
lua_pushliteral(L, "0.5");
REQUIRE(Lua::to<TestType>(L, -1) == Approx(0.5));
REQUIRE_TRY_TO(Approx(0.5));
REQUIRE(Lua::to<TestType>(L, -1) == Catch::Approx(0.5));
REQUIRE_TRY_TO(Catch::Approx(0.5));
lua_pop(L, 1);
INFO("\"test\"")
INFO("\"test\"");
lua_pushliteral(L, "test");
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("table")
INFO("table");
lua_newtable(L);
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("userdata")
INFO("userdata");
lua_newuserdata(L, 0);
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();
lua_pop(L, 1);
INFO("lightuserdata")
INFO("lightuserdata");
lua_pushlightuserdata(L, nullptr);
REQUIRE(Lua::to<TestType>(L, -1) == 0);
REQUIRE_TRY_TO_FAIL();

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