649 Zeilen
25 KiB
C++
649 Zeilen
25 KiB
C++
/**
|
|
* server/src/world/world.cpp
|
|
*
|
|
* This file is part of the traintastic source code.
|
|
*
|
|
* Copyright (C) 2019-2025 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 "world.hpp"
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/uuid/random_generator.hpp>
|
|
#include <boost/uuid/string_generator.hpp>
|
|
#include <boost/uuid/uuid_io.hpp>
|
|
|
|
#include "worldsaver.hpp"
|
|
|
|
#include "../log/log.hpp"
|
|
#include "../log/logmessageexception.hpp"
|
|
#include "../utils/datetimestr.hpp"
|
|
#include "../utils/displayname.hpp"
|
|
#include "../traintastic/traintastic.hpp"
|
|
|
|
#include "../core/method.tpp"
|
|
#include "../core/objectproperty.tpp"
|
|
#include "../core/objectvectorproperty.tpp"
|
|
#include "../core/objectlisttablemodel.hpp"
|
|
#include "../core/attributes.hpp"
|
|
#include "../core/abstractvectorproperty.hpp"
|
|
#include "../core/controllerlist.hpp"
|
|
|
|
#include "../hardware/input/input.hpp"
|
|
#include "../hardware/input/monitor/inputmonitor.hpp"
|
|
#include "../hardware/input/list/inputlist.hpp"
|
|
#include "../hardware/identification/identification.hpp"
|
|
#include "../hardware/identification/list/identificationlist.hpp"
|
|
#include "../hardware/output/keyboard/outputkeyboard.hpp"
|
|
#include "../hardware/output/list/outputlist.hpp"
|
|
#include "../hardware/interface/interfacelist.hpp"
|
|
#include "../hardware/decoder/list/decoderlist.hpp"
|
|
#include "../hardware/programming/lncv/lncvprogrammer.hpp"
|
|
#include "../hardware/programming/lncv/lncvprogrammingcontroller.hpp"
|
|
|
|
#include "../clock/clock.hpp"
|
|
|
|
#include "../board/board.hpp"
|
|
#include "../board/boardlist.hpp"
|
|
#include "../board/list/linkrailtilelist.hpp"
|
|
#include "../board/nx/nxmanager.hpp"
|
|
#include "../board/tile/rail/nxbuttonrailtile.hpp"
|
|
|
|
#include "../hardware/throttle/list/throttlelist.hpp"
|
|
#include "../train/train.hpp"
|
|
#include "../train/trainlist.hpp"
|
|
#include "../vehicle/rail/railvehiclelist.hpp"
|
|
#include "../lua/scriptlist.hpp"
|
|
#include "../status/simulationstatus.hpp"
|
|
#include "../utils/category.hpp"
|
|
|
|
using nlohmann::json;
|
|
|
|
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Interface | DecoderListColumn::Protocol | DecoderListColumn::Address;
|
|
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Interface | InputListColumn::Channel | InputListColumn::Address;
|
|
constexpr auto outputListColumns = OutputListColumn::Interface | OutputListColumn::Channel | OutputListColumn::Address;
|
|
constexpr auto identificationListColumns = IdentificationListColumn::Id | IdentificationListColumn::Name | IdentificationListColumn::Interface /*| IdentificationListColumn::Channel*/ | IdentificationListColumn::Address;
|
|
constexpr auto throttleListColumns = ThrottleListColumn::Id | ThrottleListColumn::Name | ThrottleListColumn::Train | ThrottleListColumn::Interface;
|
|
|
|
template<class T>
|
|
inline static void deleteAll(T& objectList)
|
|
{
|
|
while(!objectList.empty())
|
|
{
|
|
if constexpr(std::is_same_v<T, TrainList>)
|
|
{
|
|
if(objectList.front()->active)
|
|
{
|
|
objectList.front()->emergencyStop = true;
|
|
objectList.front()->active = false;
|
|
}
|
|
}
|
|
if constexpr(std::is_same_v<T, ThrottleList>)
|
|
{
|
|
auto& throttle = objectList[0];
|
|
throttle->destroy();
|
|
objectList.removeObject(throttle);
|
|
}
|
|
else
|
|
{
|
|
objectList.delete_(objectList.front());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<World> World::create()
|
|
{
|
|
auto world = std::make_shared<World>(Private());
|
|
init(*world);
|
|
return world;
|
|
}
|
|
|
|
void World::init(World& world)
|
|
{
|
|
world.decoderControllers.setValueInternal(std::make_shared<ControllerList<DecoderController>>(world, world.decoderControllers.name()));
|
|
world.inputControllers.setValueInternal(std::make_shared<ControllerList<InputController>>(world, world.inputControllers.name()));
|
|
world.outputControllers.setValueInternal(std::make_shared<ControllerList<OutputController>>(world, world.outputControllers.name()));
|
|
world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name()));
|
|
world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name()));
|
|
|
|
world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name()));
|
|
world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns));
|
|
world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));
|
|
world.outputs.setValueInternal(std::make_shared<OutputList>(world, world.outputs.name(), outputListColumns));
|
|
world.identifications.setValueInternal(std::make_shared<IdentificationList>(world, world.outputs.name(), identificationListColumns));
|
|
world.boards.setValueInternal(std::make_shared<BoardList>(world, world.boards.name()));
|
|
world.clock.setValueInternal(std::make_shared<Clock>(world, world.clock.name()));
|
|
world.throttles.setValueInternal(std::make_shared<ThrottleList>(world, world.throttles.name(), throttleListColumns));
|
|
world.trains.setValueInternal(std::make_shared<TrainList>(world, world.trains.name()));
|
|
world.railVehicles.setValueInternal(std::make_shared<RailVehicleList>(world, world.railVehicles.name()));
|
|
world.luaScripts.setValueInternal(std::make_shared<Lua::ScriptList>(world, world.luaScripts.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*/) :
|
|
uuid{this, "uuid", to_string(boost::uuids::random_generator()()), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
|
|
name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
|
|
scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](WorldScale /*value*/){ updateScaleRatio(); }},
|
|
scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
|
|
onlineWhenLoaded{this, "online_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
|
|
powerOnWhenLoaded{this, "power_on_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
|
|
[this](bool value)
|
|
{
|
|
if(!value)
|
|
{
|
|
runWhenLoaded = false; // can't run without power
|
|
}
|
|
}},
|
|
runWhenLoaded{this, "run_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
|
|
[this](bool value)
|
|
{
|
|
if(value)
|
|
{
|
|
powerOnWhenLoaded = true; // can't run without power
|
|
}
|
|
}},
|
|
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},
|
|
decoderControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
inputControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
identificationControllers{this, "identification_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
lncvProgrammingControllers{this, "lncv_programming_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
interfaces{this, "interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
identifications{this, "identifications", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
|
|
clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
|
|
throttles{this, "throttles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
|
|
railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
|
|
luaScripts{this, "lua_scripts", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
linkRailTiles{this, "link_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
nxManager{this, "nx_manager", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
|
|
statuses(*this, "statuses", {}, PropertyFlags::ReadOnly | PropertyFlags::Store),
|
|
hardwareThrottles{this, "hardware_throttles", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::NoScript},
|
|
state{this, "state", WorldState(), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
|
|
edit{this, "edit", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
|
|
[this](bool value)
|
|
{
|
|
if(value)
|
|
{
|
|
Log::log(*this, LogMessage::N1010_EDIT_MODE_ENABLED);
|
|
state.setValueInternal(state.value() + WorldState::Edit);
|
|
event(WorldEvent::EditEnabled);
|
|
}
|
|
else
|
|
{
|
|
Log::log(*this, LogMessage::N1011_EDIT_MODE_DISABLED);
|
|
state.setValueInternal(state.value() - WorldState::Edit);
|
|
event(WorldEvent::EditDisabled);
|
|
}
|
|
}},
|
|
offline{*this, "offline",
|
|
[this]()
|
|
{
|
|
event(WorldEvent::Offline);
|
|
}},
|
|
online{*this, "online",
|
|
[this]()
|
|
{
|
|
event(WorldEvent::Online);
|
|
}},
|
|
powerOff{*this, "power_off", MethodFlags::ScriptCallable,
|
|
[this]()
|
|
{
|
|
event(WorldEvent::PowerOff);
|
|
}},
|
|
powerOn{*this, "power_on",
|
|
[this]()
|
|
{
|
|
event(WorldEvent::PowerOn);
|
|
}},
|
|
run{*this, "run",
|
|
[this]()
|
|
{
|
|
event(WorldEvent::Run);
|
|
}},
|
|
stop{*this, "stop", MethodFlags::ScriptCallable,
|
|
[this]()
|
|
{
|
|
event(WorldEvent::Stop);
|
|
}},
|
|
mute{this, "mute", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
|
|
[this](bool value)
|
|
{
|
|
event(value ? WorldEvent::Mute : WorldEvent::Unmute);
|
|
}},
|
|
noSmoke{this, "no_smoke", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
|
|
[this](bool value)
|
|
{
|
|
event(value ? WorldEvent::NoSmoke : WorldEvent::Smoke);
|
|
}},
|
|
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 | PropertyFlags::Internal},
|
|
save{*this, "save", MethodFlags::NoScript,
|
|
[this]()
|
|
{
|
|
try
|
|
{
|
|
// backup world:
|
|
const std::filesystem::path worldDir = Traintastic::instance->worldDir();
|
|
const std::filesystem::path worldBackupDir = Traintastic::instance->worldBackupDir();
|
|
|
|
if(!std::filesystem::is_directory(worldBackupDir))
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(worldBackupDir, ec);
|
|
if(ec)
|
|
Log::log(*this, LogMessage::C1007_CREATING_WORLD_BACKUP_DIRECTORY_FAILED_X, ec);
|
|
}
|
|
|
|
if(std::filesystem::is_directory(worldDir / uuid.value()))
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::rename(worldDir / uuid.value(), worldBackupDir / uuid.value() += dateTimeStr(), ec);
|
|
if(ec)
|
|
Log::log(*this, LogMessage::C1006_CREATING_WORLD_BACKUP_FAILED_X, ec);
|
|
}
|
|
|
|
if(std::filesystem::is_regular_file(worldDir / uuid.value() += dotCTW))
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::rename(worldDir / uuid.value() += dotCTW, worldBackupDir / uuid.value() += dateTimeStr() += dotCTW, ec);
|
|
if(ec)
|
|
Log::log(*this, LogMessage::C1006_CREATING_WORLD_BACKUP_FAILED_X, ec);
|
|
}
|
|
|
|
// save world:
|
|
std::filesystem::path savePath = worldDir / uuid.value();
|
|
if(!Traintastic::instance->settings->saveWorldUncompressed)
|
|
savePath += dotCTW;
|
|
|
|
WorldSaver saver(*this, savePath);
|
|
|
|
if(Traintastic::instance)
|
|
{
|
|
Traintastic::instance->settings->lastWorld = uuid.value();
|
|
Traintastic::instance->worldList->update(*this, savePath);
|
|
}
|
|
|
|
Log::log(*this, LogMessage::N1022_SAVED_WORLD_X, name.value());
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
Log::log(*this, LogMessage::C1005_SAVING_WORLD_FAILED_X, e);
|
|
}
|
|
}}
|
|
, getObject_{*this, "get_object", MethodFlags::Internal | MethodFlags::ScriptCallable,
|
|
[this](const std::string& objectId)
|
|
{
|
|
return getObjectById(objectId);
|
|
}}
|
|
, getLNCVProgrammer{*this, "get_lncv_programmer", MethodFlags::NoScript,
|
|
[](const ObjectPtr& interface) -> std::shared_ptr<LNCVProgrammer>
|
|
{
|
|
if(auto controller = std::dynamic_pointer_cast<LNCVProgrammingController>(interface))
|
|
return std::make_shared<LNCVProgrammer>(*controller);
|
|
return {};
|
|
}}
|
|
, onEvent{*this, "on_event", EventFlags::Scriptable}
|
|
{
|
|
Attributes::addDisplayName(uuid, DisplayName::World::uuid);
|
|
m_interfaceItems.add(uuid);
|
|
Attributes::addDisplayName(name, DisplayName::Object::name);
|
|
m_interfaceItems.add(name);
|
|
Attributes::addEnabled(scale, false);
|
|
Attributes::addValues(scale, WorldScaleValues);
|
|
m_interfaceItems.add(scale);
|
|
Attributes::addEnabled(scaleRatio, false);
|
|
Attributes::addMinMax(scaleRatio, 1., 1000.);
|
|
Attributes::addVisible(scaleRatio, false);
|
|
m_interfaceItems.add(scaleRatio);
|
|
|
|
m_interfaceItems.add(onlineWhenLoaded);
|
|
m_interfaceItems.add(powerOnWhenLoaded);
|
|
m_interfaceItems.add(runWhenLoaded);
|
|
|
|
Attributes::addCategory(correctOutputPosWhenLocked, Category::trains);
|
|
Attributes::addEnabled(correctOutputPosWhenLocked, true);
|
|
m_interfaceItems.add(correctOutputPosWhenLocked);
|
|
|
|
Attributes::addCategory(extOutputChangeAction, Category::trains);
|
|
Attributes::addEnabled(extOutputChangeAction, true);
|
|
Attributes::addValues(extOutputChangeAction, extOutputChangeActionValues);
|
|
m_interfaceItems.add(extOutputChangeAction);
|
|
|
|
Attributes::addCategory(pathReleaseDelay, Category::trains);
|
|
Attributes::addEnabled(pathReleaseDelay, true);
|
|
Attributes::addMinMax(pathReleaseDelay, {0, 15000}); // Up to 15 seconds
|
|
m_interfaceItems.add(pathReleaseDelay);
|
|
|
|
Attributes::addObjectEditor(decoderControllers, false);
|
|
m_interfaceItems.add(decoderControllers);
|
|
Attributes::addObjectEditor(inputControllers, false);
|
|
m_interfaceItems.add(inputControllers);
|
|
Attributes::addObjectEditor(outputControllers, false);
|
|
m_interfaceItems.add(outputControllers);
|
|
Attributes::addObjectEditor(identificationControllers, false);
|
|
m_interfaceItems.add(identificationControllers);
|
|
Attributes::addObjectEditor(lncvProgrammingControllers, false);
|
|
m_interfaceItems.add(lncvProgrammingControllers);
|
|
|
|
Attributes::addObjectEditor(interfaces, false);
|
|
m_interfaceItems.add(interfaces);
|
|
Attributes::addObjectEditor(decoders, false);
|
|
m_interfaceItems.add(decoders);
|
|
Attributes::addObjectEditor(inputs, false);
|
|
m_interfaceItems.add(inputs);
|
|
Attributes::addObjectEditor(outputs, false);
|
|
m_interfaceItems.add(outputs);
|
|
Attributes::addObjectEditor(identifications, false);
|
|
m_interfaceItems.add(identifications);
|
|
Attributes::addObjectEditor(throttles, false);
|
|
m_interfaceItems.add(throttles);
|
|
Attributes::addObjectEditor(boards, false);
|
|
m_interfaceItems.add(boards);
|
|
Attributes::addObjectEditor(clock, false);
|
|
m_interfaceItems.add(clock);
|
|
Attributes::addObjectEditor(trains, false);
|
|
m_interfaceItems.add(trains);
|
|
Attributes::addObjectEditor(railVehicles, false);
|
|
m_interfaceItems.add(railVehicles);
|
|
Attributes::addObjectEditor(luaScripts, false);
|
|
m_interfaceItems.add(luaScripts);
|
|
|
|
Attributes::addObjectEditor(linkRailTiles, false);
|
|
m_interfaceItems.add(linkRailTiles);
|
|
Attributes::addObjectEditor(nxManager, false);
|
|
m_interfaceItems.add(nxManager);
|
|
|
|
Attributes::addObjectEditor(statuses, false);
|
|
m_interfaceItems.add(statuses);
|
|
|
|
Attributes::addObjectEditor(hardwareThrottles, false);
|
|
m_interfaceItems.add(hardwareThrottles);
|
|
|
|
Attributes::addObjectEditor(state, false);
|
|
m_interfaceItems.add(state);
|
|
Attributes::addObjectEditor(edit, false);
|
|
m_interfaceItems.add(edit);
|
|
Attributes::addObjectEditor(offline, false);
|
|
m_interfaceItems.add(offline);
|
|
Attributes::addObjectEditor(online, false);
|
|
m_interfaceItems.add(online);
|
|
Attributes::addObjectEditor(powerOff, false);
|
|
m_interfaceItems.add(powerOff);
|
|
Attributes::addObjectEditor(powerOn, false);
|
|
m_interfaceItems.add(powerOn);
|
|
Attributes::addObjectEditor(stop, false);
|
|
m_interfaceItems.add(stop);
|
|
Attributes::addObjectEditor(run, false);
|
|
m_interfaceItems.add(run);
|
|
Attributes::addObjectEditor(mute, false);
|
|
m_interfaceItems.add(mute);
|
|
Attributes::addObjectEditor(noSmoke, false);
|
|
m_interfaceItems.add(noSmoke);
|
|
Attributes::addEnabled(simulation, false);
|
|
Attributes::addObjectEditor(simulation, false);
|
|
m_interfaceItems.add(simulation);
|
|
|
|
m_interfaceItems.add(simulationStatus);
|
|
|
|
Attributes::addObjectEditor(save, false);
|
|
m_interfaceItems.add(save);
|
|
|
|
m_interfaceItems.add(getObject_);
|
|
|
|
Attributes::addObjectEditor(getLNCVProgrammer, false);
|
|
m_interfaceItems.add(getLNCVProgrammer);
|
|
|
|
m_interfaceItems.add(onEvent);
|
|
|
|
updateEnabled();
|
|
}
|
|
|
|
World::~World()
|
|
{
|
|
deleteAll(*interfaces);
|
|
deleteAll(*decoders);
|
|
deleteAll(*inputs);
|
|
deleteAll(*identifications);
|
|
deleteAll(*boards);
|
|
deleteAll(*throttles);
|
|
deleteAll(*trains);
|
|
deleteAll(*railVehicles);
|
|
deleteAll(*luaScripts);
|
|
luaScripts.setValueInternal(nullptr);
|
|
}
|
|
|
|
std::string World::getUniqueId(std::string_view prefix) const
|
|
{
|
|
std::string uniqueId{prefix};
|
|
uniqueId.append("_");
|
|
uint32_t number = 0;
|
|
do
|
|
{
|
|
uniqueId.resize(prefix.size() + 1);
|
|
uniqueId.append(std::to_string(++number));
|
|
}
|
|
while(isObject(uniqueId));
|
|
|
|
return uniqueId;
|
|
}
|
|
|
|
bool World::isObject(const std::string& _id) const
|
|
{
|
|
return m_objects.find(_id) != m_objects.end() || _id == id || _id == Traintastic::id;
|
|
}
|
|
|
|
ObjectPtr World::getObjectById(const std::string& _id) const
|
|
{
|
|
auto it = m_objects.find(_id);
|
|
if(it != m_objects.end())
|
|
return it->second.lock();
|
|
if(_id == classId)
|
|
return std::const_pointer_cast<Object>(shared_from_this());
|
|
return ObjectPtr();
|
|
}
|
|
|
|
ObjectPtr World::getObjectByPath(std::string_view path) const
|
|
{
|
|
std::vector<std::string> ids;
|
|
boost::split(ids, path, [](char c){ return c == '.'; });
|
|
auto it = ids.cbegin();
|
|
|
|
ObjectPtr obj = getObjectById(*it);
|
|
while(obj && ++it != ids.cend())
|
|
{
|
|
if(AbstractProperty* property = obj->getProperty(*it); property && property->type() == ValueType::Object)
|
|
obj = property->toObject();
|
|
else if(AbstractVectorProperty* vectorProperty = obj->getVectorProperty(*it); vectorProperty && vectorProperty->type() == ValueType::Object)
|
|
{
|
|
obj.reset();
|
|
const size_t size = vectorProperty->size();
|
|
for(size_t i = 0; i < size; i++)
|
|
{
|
|
ObjectPtr v = vectorProperty->getObject(i);
|
|
if(path == v->getObjectId())
|
|
{
|
|
obj = v;
|
|
it++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
obj.reset();
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
void World::export_(std::vector<std::byte>& data)
|
|
{
|
|
try
|
|
{
|
|
WorldSaver saver(*this, data);
|
|
Log::log(*this, LogMessage::N1025_EXPORTED_WORLD_SUCCESSFULLY);
|
|
//return true;
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
throw LogMessageException(LogMessage::C1010_EXPORTING_WORLD_FAILED_X, e);
|
|
}
|
|
}
|
|
|
|
void World::loaded()
|
|
{
|
|
updateScaleRatio();
|
|
Object::loaded();
|
|
}
|
|
|
|
void World::worldEvent(WorldState worldState, WorldEvent worldEvent)
|
|
{
|
|
Object::worldEvent(worldState, worldEvent);
|
|
|
|
const bool editState = contains(worldState, WorldState::Edit);
|
|
const bool runState = contains(worldState, WorldState::Run);
|
|
|
|
Attributes::setEnabled(scale, editState && !runState);
|
|
Attributes::setEnabled(scaleRatio, editState && !runState);
|
|
|
|
fireEvent(onEvent, worldState, worldEvent);
|
|
}
|
|
|
|
void World::event(const WorldEvent value)
|
|
{
|
|
// Update state:
|
|
switch(value)
|
|
{
|
|
case WorldEvent::EditDisabled:
|
|
state.setValueInternal(state.value() - WorldState::Edit);
|
|
break;
|
|
|
|
case WorldEvent::EditEnabled:
|
|
state.setValueInternal(state.value() + WorldState::Edit);
|
|
break;
|
|
|
|
case WorldEvent::Offline:
|
|
Log::log(*this, LogMessage::N1013_COMMUNICATION_DISABLED);
|
|
state.setValueInternal(state.value() - WorldState::Online);
|
|
break;
|
|
|
|
case WorldEvent::Online:
|
|
Log::log(*this, LogMessage::N1012_COMMUNICATION_ENABLED);
|
|
state.setValueInternal(state.value() + WorldState::Online);
|
|
break;
|
|
|
|
case WorldEvent::PowerOff:
|
|
Log::log(*this, LogMessage::N1015_POWER_OFF);
|
|
state.setValueInternal(state.value() - WorldState::PowerOn - WorldState::Run);
|
|
break;
|
|
|
|
case WorldEvent::PowerOn:
|
|
Log::log(*this, LogMessage::N1014_POWER_ON);
|
|
state.setValueInternal(state.value() + WorldState::PowerOn);
|
|
break;
|
|
|
|
case WorldEvent::Stop:
|
|
Log::log(*this, LogMessage::N1017_STOPPED);
|
|
state.setValueInternal(state.value() - WorldState::Run);
|
|
break;
|
|
|
|
case WorldEvent::Run:
|
|
Log::log(*this, LogMessage::N1016_RUNNING);
|
|
state.setValueInternal(state.value() + WorldState::PowerOn + WorldState::Run);
|
|
break;
|
|
|
|
case WorldEvent::Unmute:
|
|
Log::log(*this, LogMessage::N1019_MUTE_DISABLED);
|
|
state.setValueInternal(state.value() - WorldState::Mute);
|
|
break;
|
|
|
|
case WorldEvent::Mute:
|
|
Log::log(*this, LogMessage::N1018_MUTE_ENABLED);
|
|
state.setValueInternal(state.value() + WorldState::Mute);
|
|
break;
|
|
|
|
case WorldEvent::NoSmoke:
|
|
Log::log(*this, LogMessage::N1021_SMOKE_DISABLED);
|
|
state.setValueInternal(state.value() + WorldState::NoSmoke);
|
|
break;
|
|
|
|
case WorldEvent::Smoke:
|
|
Log::log(*this, LogMessage::N1020_SMOKE_ENABLED);
|
|
state.setValueInternal(state.value() - WorldState::NoSmoke);
|
|
break;
|
|
|
|
case WorldEvent::SimulationDisabled:
|
|
Log::log(*this, LogMessage::N1023_SIMULATION_DISABLED);
|
|
state.setValueInternal(state.value() - WorldState::Simulation);
|
|
break;
|
|
|
|
case WorldEvent::SimulationEnabled:
|
|
Log::log(*this, LogMessage::N1024_SIMULATION_ENABLED);
|
|
state.setValueInternal(state.value() + WorldState::Simulation);
|
|
break;
|
|
}
|
|
|
|
updateEnabled();
|
|
|
|
const WorldState worldState = state;
|
|
worldEvent(worldState, value);
|
|
for(auto& it : m_objects)
|
|
it.second.lock()->worldEvent(worldState, value);
|
|
}
|
|
|
|
void World::updateEnabled()
|
|
{
|
|
const bool isOnline = contains(state.value(), WorldState::Online);
|
|
const bool isPoweredOn = contains(state.value(), WorldState::PowerOn);
|
|
const bool isRunning = contains(state.value(), WorldState::Run);
|
|
|
|
Attributes::setEnabled(simulation, !isOnline && !isPoweredOn && !isRunning);
|
|
}
|
|
|
|
void World::updateScaleRatio()
|
|
{
|
|
if(scale != WorldScale::Custom)
|
|
{
|
|
scaleRatio.setValueInternal(getScaleRatio(scale));
|
|
Attributes::setVisible(scaleRatio, false);
|
|
}
|
|
else
|
|
Attributes::setVisible(scaleRatio, true);
|
|
}
|