[world] added scripting feature settings: show/hid scripting related stuff, see #71

Dieser Commit ist enthalten in:
Reinder Feenstra 2026-01-08 00:10:24 +01:00
Ursprung de717fef67
Commit 9a387bdaac
14 geänderte Dateien mit 335 neuen und 59 gelöschten Zeilen

Datei anzeigen

@ -1,9 +1,8 @@
/**
* client/src/mainwindow.cpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -765,6 +764,19 @@ void MainWindow::worldChanged()
m_worldSimulationAction->setEnabled(simulation->getAttributeBool(AttributeName::Enabled, true));
}
if(auto* property = m_world->getProperty("lua_scripts")) [[likely]]
{
connect(property, &AbstractProperty::attributeChanged,
[this](AttributeName name, const QVariant& value)
{
if(name == AttributeName::Visible)
{
m_actionLuaScript->setVisible(value.toBool());
}
});
m_actionLuaScript->setVisible(property->getAttributeBool(AttributeName::Visible, true));
}
}
static_cast<MainWindowStatusBar*>(statusBar())->worldChanged();
@ -1079,8 +1091,6 @@ void MainWindow::updateActions()
worldStateChanged(haveWorld ? m_connection->world()->getProperty("state")->toInt64() : 0);
setMenuEnabled(m_menuObjects, haveWorld);
if(haveWorld && !m_connection->world()->hasProperty("lua_scripts"))
m_actionLuaScript->setEnabled(false);
if(connected && !haveWorld)
{

Datei anzeigen

@ -1,7 +1,8 @@
/**
* server/src/core/idobject.cpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2026 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,6 +47,7 @@ IdObject::IdObject(World& world, std::string_view _id) :
Attributes::addDisplayName(id, DisplayName::Object::id);
Attributes::addEnabled(id, editable);
Attributes::addVisible(id, m_world.feature(WorldFeature::Scripting));
m_interfaceItems.add(id);
}
@ -57,6 +59,12 @@ void IdObject::destroying()
Object::destroying();
}
void IdObject::loaded()
{
Object::loaded();
Attributes::setVisible(id, m_world.feature(WorldFeature::Scripting));
}
void IdObject::addToWorld()
{
m_world.m_objects.emplace(id, weak_from_this());
@ -68,3 +76,10 @@ void IdObject::worldEvent(WorldState state, WorldEvent event)
Attributes::setEnabled(id, contains(state, WorldState::Edit));
}
void IdObject::worldFeaturesChanged(const WorldFeatures features, WorldFeature changed)
{
Object::worldFeaturesChanged(features, changed);
Attributes::setVisible(id, features[WorldFeature::Scripting]);
}

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/core/idobject.hpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -60,8 +59,10 @@ class IdObject : public Object
IdObject(World& world, std::string_view _id);
void destroying() override;
void loaded() override;
virtual void addToWorld();
void worldEvent(WorldState state, WorldEvent event) override;
void worldFeaturesChanged(const WorldFeatures features, WorldFeature changed) override;
public:
Property<std::string> id;

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/core/object.cpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -28,6 +27,7 @@
#include "abstractproperty.hpp"
#include "abstractobjectproperty.hpp"
#include "abstractvectorproperty.hpp"
#include "../world/worldfeatures.hpp"
#include "../world/worldloader.hpp"
#include "../world/worldsaver.hpp"
@ -165,14 +165,49 @@ void Object::worldEvent(WorldState state, WorldEvent event)
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(&it.second);
property && contains(property->flags(), PropertyFlags::SubObject))
{
property->toObject()->worldEvent(state, event);
if(auto object = property->toObject())
{
object->worldEvent(state, event);
}
}
else if(AbstractVectorProperty* vectorProperty = dynamic_cast<AbstractVectorProperty*>(&it.second);
vectorProperty && contains(vectorProperty->flags(), PropertyFlags::SubObject))
{
const size_t size = vectorProperty->size();
for(size_t i = 0; i < size; i++)
vectorProperty->getObject(i)->worldEvent(state, event);
{
if(auto object = vectorProperty->getObject(i)) [[likely]]
{
object->worldEvent(state, event);
}
}
}
}
}
void Object::worldFeaturesChanged(WorldFeatures features, WorldFeature changed)
{
for(const auto& it : m_interfaceItems)
{
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(&it.second);
property && contains(property->flags(), PropertyFlags::SubObject))
{
if(auto object = property->toObject())
{
object->worldFeaturesChanged(features, changed);
}
}
else if(AbstractVectorProperty* vectorProperty = dynamic_cast<AbstractVectorProperty*>(&it.second);
vectorProperty && contains(vectorProperty->flags(), PropertyFlags::SubObject))
{
const size_t size = vectorProperty->size();
for(size_t i = 0; i < size; i++)
{
if(auto object = vectorProperty->getObject(i)) [[likely]]
{
object->worldFeaturesChanged(features, changed);
}
}
}
}
}

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/core/object.hpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -44,6 +43,8 @@ class AbstractObjectProperty;
class AbstractVectorProperty;
class AbstractAttribute;
class AbstractEvent;
enum class WorldFeature;
class WorldFeatures;
class WorldLoader;
class WorldSaver;
@ -91,6 +92,7 @@ class Object : public std::enable_shared_from_this<Object>
virtual void save(WorldSaver& saver, nlohmann::json& data, nlohmann::json& state) const;
virtual void loaded();
virtual void worldEvent(WorldState state, WorldEvent event);
virtual void worldFeaturesChanged(WorldFeatures features, WorldFeature changed);
public:
Object(const Object&) = delete;

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/lua/script.cpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -155,12 +154,19 @@ void Script::loaded()
break;
case LuaScriptState::Running:
startSandbox();
if(state == LuaScriptState::Running)
if(m_world.feature(WorldFeature::Scripting))
{
updateEnabled(); // setState doesn't updateEnabled() because the state is already running
auto& running = m_world.luaScripts->status->running;
running.setValueInternal(running + 1); // setState doesn't increment because the state is already running
startSandbox();
if(state == LuaScriptState::Running)
{
updateEnabled(); // setState doesn't updateEnabled() because the state is already running
auto& running = m_world.luaScripts->status->running;
running.setValueInternal(running + 1); // setState doesn't increment because the state is already running
}
}
else
{
state.setValueInternal(LuaScriptState::Stopped);
}
break;
@ -176,6 +182,18 @@ void Script::worldEvent(WorldState worldState, WorldEvent worldEvent)
updateEnabled();
}
void Script::worldFeaturesChanged(const WorldFeatures features, WorldFeature changed)
{
IdObject::worldFeaturesChanged(features, changed);
if(!features[WorldFeature::Scripting] && m_sandbox)
{
stopSandbox();
}
updateEnabled();
}
void Script::updateEnabled()
{
const bool editable = contains(m_world.state.value(), WorldState::Edit) && state != LuaScriptState::Running;
@ -186,7 +204,7 @@ void Script::updateEnabled()
Attributes::setEnabled(disabled, editable);
Attributes::setEnabled(code, editable);
Attributes::setEnabled(start, stoppedOrError);
Attributes::setEnabled(start, stoppedOrError && m_world.feature(WorldFeature::Scripting));
Attributes::setEnabled(stop, state == LuaScriptState::Running);
Attributes::setEnabled(clearPersistentVariables, stoppedOrError && !m_persistentVariables.empty());
@ -231,6 +249,12 @@ void Script::setState(LuaScriptState value)
void Script::startSandbox()
{
assert(!m_sandbox);
if(!m_world.feature(WorldFeature::Scripting))
{
return;
}
if((m_sandbox = Sandbox::create(*this)))
{
Log::log(*this, LogMessage::N9001_STARTING_SCRIPT);

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/lua/script.hpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 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,6 +46,7 @@ class Script : public IdObject
void destroying() final;
void loaded() final;
void worldEvent(WorldState worldState, WorldEvent worldEvent) final;
void worldFeaturesChanged(const WorldFeatures features, WorldFeature changed) final;
void updateEnabled();
void setState(LuaScriptState value);

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/lua/scriptlist.cpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -93,7 +92,7 @@ ScriptList::ScriptList(Object& _parent, std::string_view parentPropertyName)
ScriptList::~ScriptList()
{
getWorld(parent()).statuses.removeInternal(status.value());
removeStatus();
}
TableModelPtr ScriptList::getModel()
@ -101,6 +100,16 @@ TableModelPtr ScriptList::getModel()
return std::make_shared<ScriptListTableModel>(*this);
}
void ScriptList::loaded()
{
ObjectList<Script>::loaded();
if(!getWorld(parent()).feature(WorldFeature::Scripting))
{
removeStatus();
}
}
void ScriptList::worldEvent(WorldState state, WorldEvent event)
{
ObjectList<Script>::worldEvent(state, event);
@ -111,11 +120,28 @@ void ScriptList::worldEvent(WorldState state, WorldEvent event)
Attributes::setEnabled(delete_, editable);
}
void ScriptList::worldFeaturesChanged(const WorldFeatures features, WorldFeature changed)
{
ObjectList<Script>::worldFeaturesChanged(features, changed);
if(changed == WorldFeature::Scripting && !empty())
{
if(features[WorldFeature::Scripting])
{
addStatus();
}
else if(!features[WorldFeature::Scripting])
{
removeStatus();
}
}
}
void ScriptList::objectAdded(const std::shared_ptr<Script>& /*object*/)
{
if(m_items.size() == 1)
{
getWorld(parent()).statuses.appendInternal(status.value());
addStatus();
}
updateEnabled();
}
@ -124,7 +150,7 @@ void ScriptList::objectRemoved(const std::shared_ptr<Script>& /*object*/)
{
if(empty())
{
getWorld(parent()).statuses.removeInternal(status.value());
removeStatus();
}
updateEnabled();
}
@ -152,4 +178,14 @@ void ScriptList::updateEnabled()
Attributes::setEnabled(clearPersistentVariables, canClearPersistentVariables);
}
void ScriptList::addStatus()
{
getWorld(parent()).statuses.appendInternal(status.value());
}
void ScriptList::removeStatus()
{
getWorld(parent()).statuses.removeInternal(status.value());
}
}

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/lua/scriptlist.hpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -34,7 +33,9 @@ namespace Lua {
class ScriptList final : public ObjectList<Script>
{
protected:
void loaded() final;
void worldEvent(WorldState state, WorldEvent event) final;
void worldFeaturesChanged(const WorldFeatures features, WorldFeature changed) final;
void objectAdded(const std::shared_ptr<Script>& /*object*/) final;
void objectRemoved(const std::shared_ptr<Script>& /*object*/) final;
bool isListedProperty(std::string_view name) final;
@ -55,6 +56,10 @@ class ScriptList final : public ObjectList<Script>
TableModelPtr getModel() final;
void updateEnabled();
private:
void addStatus();
void removeStatus();
};
}

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/utils/category.hpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2025 Reinder Feenstra
* Copyright (C) 2021-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -34,6 +33,7 @@ namespace Category
constexpr std::string_view debug = "category:debug";
constexpr std::string_view developer = "category:developer";
constexpr std::string_view driver = "category:driver";
constexpr std::string_view features = "category:features";
constexpr std::string_view general = "category:general";
constexpr std::string_view info = "category:info";
constexpr std::string_view input = "category:input";

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/world/world.cpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -173,6 +172,11 @@ World::World(Private /*unused*/) :
correctOutputPosWhenLocked{this, "correct_output_pos_when_locked", true, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
extOutputChangeAction{this, "ext_output_change_action", ExternalOutputChangeAction::EmergencyStopTrain, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
pathReleaseDelay{this, "path_release_delay", 5000, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
featureScripting{this, "feature_scripting", true, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](bool value)
{
setFeature(WorldFeature::Scripting, value);
}},
debugBlockEvents{this, "debug_block_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
debugTrainEvents{this, "debug_train_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
debugZoneEvents{this, "debug_zone_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
@ -369,6 +373,10 @@ World::World(Private /*unused*/) :
Attributes::addMinMax(pathReleaseDelay, {0, 15000}); // Up to 15 seconds
m_interfaceItems.add(pathReleaseDelay);
// Features:
Attributes::addCategory(featureScripting, Category::features);
m_interfaceItems.add(featureScripting);
// Debug options:
Attributes::addCategory(debugBlockEvents, Category::debug);
m_interfaceItems.add(debugBlockEvents);
@ -417,6 +425,7 @@ World::World(Private /*unused*/) :
Attributes::addObjectEditor(railVehicles, false);
m_interfaceItems.add(railVehicles);
Attributes::addObjectEditor(luaScripts, false);
Attributes::addVisible(luaScripts, featureScripting);
m_interfaceItems.add(luaScripts);
Attributes::addObjectEditor(blockRailTiles, false);
@ -470,6 +479,7 @@ World::World(Private /*unused*/) :
m_interfaceItems.add(onEvent);
updateEnabled();
updateFeatures();
}
World::~World()
@ -565,6 +575,7 @@ void World::export_(std::vector<std::byte>& data)
void World::loaded()
{
updateFeatures();
updateScaleRatio();
Object::loaded();
}
@ -582,6 +593,13 @@ void World::worldEvent(WorldState worldState, WorldEvent worldEvent)
fireEvent(onEvent, worldState, worldEvent);
}
void World::worldFeaturesChanged(const WorldFeatures features, WorldFeature changed)
{
Object::worldFeaturesChanged(features, changed);
Attributes::setVisible(luaScripts, features[WorldFeature::Scripting]);
}
void World::event(const WorldEvent value)
{
// Update state:
@ -664,6 +682,20 @@ void World::event(const WorldEvent value)
it.second.lock()->worldEvent(worldState, value);
}
void World::setFeature(WorldFeature feature, bool value)
{
if(m_features[feature] != value)
{
m_features.set(feature, value);
worldFeaturesChanged(m_features, feature);
for(auto& it : m_objects)
{
it.second.lock()->worldFeaturesChanged(m_features, feature);
}
}
}
void World::updateEnabled()
{
const bool isOnline = contains(state.value(), WorldState::Online);
@ -673,6 +705,12 @@ void World::updateEnabled()
Attributes::setEnabled(simulation, !isOnline && !isPoweredOn && !isRunning);
}
void World::updateFeatures()
{
m_features.set(WorldFeature::Scripting, featureScripting);
Attributes::setVisible(luaScripts, feature(WorldFeature::Scripting));
}
void World::updateScaleRatio()
{
if(scale != WorldScale::Custom)

Datei anzeigen

@ -1,9 +1,8 @@
/**
* server/src/world/world.hpp
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 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 +22,7 @@
#ifndef TRAINTASTIC_SERVER_WORLD_WORLD_HPP
#define TRAINTASTIC_SERVER_WORLD_WORLD_HPP
#include "worldfeatures.hpp"
#include "../core/object.hpp"
#include "../core/property.hpp"
#include "../core/objectproperty.hpp"
@ -80,7 +80,10 @@ class World : public Object
private:
struct Private {};
WorldFeatures m_features;
void updateEnabled();
void updateFeatures();
void updateScaleRatio();
protected:
@ -90,7 +93,10 @@ class World : public Object
void loaded() final;
void worldEvent(WorldState worldState, WorldEvent worldEvent) final;
void worldFeaturesChanged(const WorldFeatures features, WorldFeature changed) final;
void event(WorldEvent value);
void setFeature(WorldFeature feature, bool value);
public:
CLASS_ID("world")
@ -114,6 +120,8 @@ class World : public Object
Property<ExternalOutputChangeAction> extOutputChangeAction;
Property<uint16_t> pathReleaseDelay;
Property<bool> featureScripting;
Property<bool> debugBlockEvents;
Property<bool> debugTrainEvents;
Property<bool> debugZoneEvents;
@ -170,6 +178,16 @@ class World : public Object
World(Private);
~World() override;
inline bool feature(WorldFeature feature) const
{
return m_features[feature];
}
const WorldFeatures features() const
{
return m_features;
}
std::string getObjectId() const final { return std::string(classId); }
std::string getUniqueId(std::string_view prefix) const;

Datei anzeigen

@ -0,0 +1,84 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2026 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_WORLD_WORLDFEATURES_HPP
#define TRAINTASTIC_SERVER_WORLD_WORLDFEATURES_HPP
#include <cstdint>
#include <array>
enum class WorldFeature
{
Scripting = 0,
};
class WorldFeatures
{
public:
constexpr WorldFeatures() noexcept
: m_features{0}
{
}
constexpr WorldFeatures(std::initializer_list<WorldFeature> features) noexcept
: WorldFeatures()
{
for(auto f : features)
{
set(f, true);
}
}
constexpr bool operator [](WorldFeature f) const noexcept
{
return (m_features[index(f)] & bit(f)) != 0;
}
constexpr void set(WorldFeature f, bool enable) noexcept
{
if(enable)
{
m_features[index(f)] |= bit(f);
}
else
{
m_features[index(f)] &= ~bit(f);
}
}
private:
using ET = uint8_t;
using UT = std::underlying_type_t<WorldFeature>;
std::array<ET, 1> m_features;
static constexpr std::size_t index(WorldFeature f) noexcept
{
return static_cast<UT>(f) / (sizeof(ET) * 8);
}
static constexpr ET bit(WorldFeature f) noexcept
{
return static_cast<ET>(1) << (static_cast<UT>(f) % (sizeof(ET) * 8));
}
};
#endif

Datei anzeigen

@ -251,6 +251,10 @@
"term": "category:developer",
"definition": "Developer"
},
{
"term": "category:features",
"definition": "Features"
},
{
"term": "category:general",
"definition": "General"
@ -3215,6 +3219,10 @@
"term": "world:ext_output_change_action",
"definition": "External output change action"
},
{
"term": "world:feature_scripting",
"definition": "Scripting"
},
{
"term": "world:inputs",
"definition": "Inputs"