• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

traintastic / traintastic / 20638780725

01 Jan 2026 12:44PM UTC coverage: 27.155% (-0.4%) from 27.527%
20638780725

push

github

reinder
bumped copyright year to 2026

7873 of 28993 relevant lines covered (27.15%)

191.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.1
/server/src/world/world.cpp
1
/**
2
 * server/src/world/world.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2019-2025 Reinder Feenstra
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * as published by the Free Software Foundation; either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
 */
22

23
#include "world.hpp"
24

25
#include <boost/algorithm/string.hpp>
26
#include <boost/uuid/random_generator.hpp>
27
#include <boost/uuid/string_generator.hpp>
28
#include <boost/uuid/uuid_io.hpp>
29

30
#include "worldsaver.hpp"
31

32
#include "../log/log.hpp"
33
#include "../log/logmessageexception.hpp"
34
#include "../utils/datetimestr.hpp"
35
#include "../utils/displayname.hpp"
36
#include "../traintastic/traintastic.hpp"
37

38
#include "../core/method.tpp"
39
#include "../core/objectproperty.tpp"
40
#include "../core/objectvectorproperty.tpp"
41
#include "../core/objectlisttablemodel.hpp"
42
#include "../core/attributes.hpp"
43
#include "../core/abstractvectorproperty.hpp"
44
#include "../core/controllerlist.hpp"
45

46
#include "../hardware/booster/booster.hpp"
47
#include "../hardware/booster/list/boosterlist.hpp"
48
#include "../hardware/input/input.hpp"
49
#include "../hardware/input/monitor/inputmonitor.hpp"
50
#include "../hardware/input/list/inputlist.hpp"
51
#include "../hardware/identification/identification.hpp"
52
#include "../hardware/identification/list/identificationlist.hpp"
53
#include "../hardware/output/keyboard/outputkeyboard.hpp"
54
#include "../hardware/output/list/outputlist.hpp"
55
#include "../hardware/interface/interfacelist.hpp"
56
#include "../hardware/decoder/list/decoderlist.hpp"
57
#include "../hardware/programming/lncv/lncvprogrammer.hpp"
58
#include "../hardware/programming/lncv/lncvprogrammingcontroller.hpp"
59

60
#include "../clock/clock.hpp"
61

62
#include "../board/board.hpp"
63
#include "../board/boardlist.hpp"
64
#include "../board/list/blockrailtilelist.hpp"
65
#include "../board/list/linkrailtilelist.hpp"
66
#include "../board/nx/nxmanager.hpp"
67
#include "../board/tile/rail/nxbuttonrailtile.hpp"
68

69
#include "../zone/zone.hpp"
70
#include "../zone/zonelist.hpp"
71

72
#include "../throttle/list/throttlelist.hpp"
73
#include "../train/train.hpp"
74
#include "../train/trainlist.hpp"
75
#include "../vehicle/rail/railvehiclelist.hpp"
76
#include "../lua/scriptlist.hpp"
77
#include "../status/simulationstatus.hpp"
78
#include "../utils/category.hpp"
79

80
using nlohmann::json;
81

82
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Interface | DecoderListColumn::Protocol | DecoderListColumn::Address;
83
constexpr auto inputListColumns = InputListColumn::Interface | InputListColumn::Channel | InputListColumn::Address;
84
constexpr auto outputListColumns = OutputListColumn::Interface | OutputListColumn::Channel | OutputListColumn::Address;
85
constexpr auto identificationListColumns = IdentificationListColumn::Id | IdentificationListColumn::Name | IdentificationListColumn::Interface /*| IdentificationListColumn::Channel*/ | IdentificationListColumn::Address;
86
constexpr auto throttleListColumns = ThrottleListColumn::Name | ThrottleListColumn::Train | ThrottleListColumn::Interface;
87

88
template<class T>
89
inline static void deleteAll(T& objectList)
1,664✔
90
{
91
  while(!objectList.empty())
1,923✔
92
  {
93
    if constexpr(std::is_same_v<T, TrainList>)
94
    {
95
      if(objectList.front()->active)
19✔
96
      {
97
        objectList.front()->emergencyStop = true;
9✔
98
        objectList.front()->active = false;
9✔
99
      }
100
    }
101
    if constexpr(std::is_same_v<T, ThrottleList>)
102
    {
103
      auto& throttle = objectList[0];
×
104
      throttle->destroy();
×
105
      objectList.removeObject(throttle);
×
106
    }
107
    else
108
    {
109
      objectList.delete_(objectList.front());
259✔
110
    }
111
  }
112
}
1,664✔
113

114
std::shared_ptr<World> World::create()
208✔
115
{
116
  auto world = std::make_shared<World>(Private());
208✔
117
  init(*world);
208✔
118
  return world;
208✔
119
}
×
120

121
void World::init(World& world)
208✔
122
{
123
  world.decoderControllers.setValueInternal(std::make_shared<ControllerList<DecoderController>>(world, world.decoderControllers.name()));
208✔
124
  world.inputControllers.setValueInternal(std::make_shared<ControllerList<InputController>>(world, world.inputControllers.name()));
208✔
125
  world.outputControllers.setValueInternal(std::make_shared<ControllerList<OutputController>>(world, world.outputControllers.name()));
208✔
126
  world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name()));
208✔
127
  world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name()));
208✔
128
  world.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.name()));
208✔
129

130
  world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name()));
208✔
131
  world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns));
208✔
132
  world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));
208✔
133
  world.outputs.setValueInternal(std::make_shared<OutputList>(world, world.outputs.name(), outputListColumns));
208✔
134
  world.identifications.setValueInternal(std::make_shared<IdentificationList>(world, world.outputs.name(), identificationListColumns));
208✔
135
  world.boosters.setValueInternal(std::make_shared<BoosterList>(world, world.boosters.name()));
208✔
136
  world.boards.setValueInternal(std::make_shared<BoardList>(world, world.boards.name()));
208✔
137
  world.zones.setValueInternal(std::make_shared<ZoneList>(world, world.zones.name()));
208✔
138
  world.clock.setValueInternal(std::make_shared<Clock>(world, world.clock.name()));
208✔
139
  world.throttles.setValueInternal(std::make_shared<ThrottleList>(world, world.throttles.name(), throttleListColumns));
208✔
140
  world.trains.setValueInternal(std::make_shared<TrainList>(world, world.trains.name()));
208✔
141
  world.railVehicles.setValueInternal(std::make_shared<RailVehicleList>(world, world.railVehicles.name()));
208✔
142
  world.luaScripts.setValueInternal(std::make_shared<Lua::ScriptList>(world, world.luaScripts.name()));
208✔
143

144
  world.blockRailTiles.setValueInternal(std::make_shared<BlockRailTileList>(world, world.blockRailTiles.name()));
208✔
145
  world.linkRailTiles.setValueInternal(std::make_shared<LinkRailTileList>(world, world.linkRailTiles.name()));
208✔
146
  world.nxManager.setValueInternal(std::make_shared<NXManager>(world, world.nxManager.name()));
208✔
147

148
  world.simulationStatus.setValueInternal(std::make_shared<SimulationStatus>(world, world.simulationStatus.name()));
208✔
149
}
208✔
150

151
World::World(Private /*unused*/) :
208✔
152
  uuid{this, "uuid", to_string(boost::uuids::random_generator()()), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
208✔
153
  name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
416✔
154
  scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](WorldScale /*value*/){ updateScaleRatio(); }},
208✔
155
  scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
208✔
156
  onlineWhenLoaded{this, "online_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
157
  powerOnWhenLoaded{this, "power_on_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
416✔
158
    [this](bool value)
208✔
159
    {
160
      if(!value)
×
161
      {
162
        runWhenLoaded = false; // can't run without power
×
163
      }
164
    }},
×
165
  runWhenLoaded{this, "run_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
416✔
166
    [this](bool value)
208✔
167
    {
168
      if(value)
×
169
      {
170
        powerOnWhenLoaded = true; // can't run without power
×
171
      }
172
    }},
×
173
  correctOutputPosWhenLocked{this, "correct_output_pos_when_locked", true, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
174
  extOutputChangeAction{this, "ext_output_change_action", ExternalOutputChangeAction::EmergencyStopTrain, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
175
  pathReleaseDelay{this, "path_release_delay", 5000, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
176
  debugBlockEvents{this, "debug_block_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
177
  debugTrainEvents{this, "debug_train_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
178
  debugZoneEvents{this, "debug_zone_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
208✔
179
  decoderControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
180
  inputControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
181
  outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
182
  identificationControllers{this, "identification_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
183
  lncvProgrammingControllers{this, "lncv_programming_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
184
  loconetInterfaces{this, "loconet_interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
185
  interfaces{this, "interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
186
  decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
187
  inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
188
  outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
189
  identifications{this, "identifications", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
190
  boosters{this, "boosters", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
191
  boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
208✔
192
  zones{this, "zones", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
208✔
193
  clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
208✔
194
  throttles{this, "throttles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
195
  trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
208✔
196
  railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
208✔
197
  luaScripts{this, "lua_scripts", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
198
  blockRailTiles{this, "block_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
199
  linkRailTiles{this, "link_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
200
  nxManager{this, "nx_manager", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
208✔
201
  statuses(*this, "statuses", {}, PropertyFlags::ReadOnly | PropertyFlags::Store),
208✔
202
  hardwareThrottles{this, "hardware_throttles", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::NoScript},
208✔
203
  state{this, "state", WorldState(), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
208✔
204
  edit{this, "edit", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
416✔
205
    [this](bool value)
208✔
206
    {
207
      if(value)
2✔
208
      {
209
        Log::log(*this, LogMessage::N1010_EDIT_MODE_ENABLED);
1✔
210
        state.setValueInternal(state.value() + WorldState::Edit);
1✔
211
        event(WorldEvent::EditEnabled);
1✔
212
      }
213
      else
214
      {
215
        Log::log(*this, LogMessage::N1011_EDIT_MODE_DISABLED);
1✔
216
        state.setValueInternal(state.value() - WorldState::Edit);
1✔
217
        event(WorldEvent::EditDisabled);
1✔
218
      }
219
    }},
2✔
220
  offline{*this, "offline",
416✔
221
    [this]()
208✔
222
    {
223
      event(WorldEvent::Offline);
×
224
    }},
×
225
  online{*this, "online",
416✔
226
    [this]()
208✔
227
    {
228
      event(WorldEvent::Online);
×
229
    }},
×
230
  powerOff{*this, "power_off", MethodFlags::ScriptCallable,
416✔
231
    [this]()
208✔
232
    {
233
      event(WorldEvent::PowerOff);
×
234
    }},
×
235
  powerOn{*this, "power_on",
416✔
236
    [this]()
208✔
237
    {
238
      event(WorldEvent::PowerOn);
×
239
    }},
×
240
  run{*this, "run",
416✔
241
    [this]()
208✔
242
    {
243
      event(WorldEvent::Run);
8✔
244
    }},
8✔
245
  stop{*this, "stop", MethodFlags::ScriptCallable,
416✔
246
    [this]()
208✔
247
    {
248
      event(WorldEvent::Stop);
2✔
249
    }},
2✔
250
  mute{this, "mute", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
416✔
251
    [this](bool value)
208✔
252
    {
253
      event(value ? WorldEvent::Mute : WorldEvent::Unmute);
×
254
    }},
×
255
  noSmoke{this, "no_smoke", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
416✔
256
    [this](bool value)
208✔
257
    {
258
      event(value ? WorldEvent::NoSmoke : WorldEvent::Smoke);
×
259
    }},
×
260
  simulation{this, "simulation", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
416✔
261
    [this](bool value)
208✔
262
    {
263
      simulationStatus->enabled.setValueInternal(value);
1✔
264
      if(value)
1✔
265
      {
266
        statuses.appendInternal(simulationStatus.value());
1✔
267
      }
268
      else
269
      {
270
        statuses.removeInternal(simulationStatus.value());
×
271
      }
272
      event(value ? WorldEvent::SimulationEnabled : WorldEvent::SimulationDisabled);
1✔
273
    }},
1✔
274
  simulationStatus{this, "simulation_status", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::Internal},
208✔
275
  save{*this, "save", MethodFlags::NoScript,
416✔
276
    [this]()
208✔
277
    {
278
      try
279
      {
280
        // backup world:
281
        const std::filesystem::path worldDir = Traintastic::instance->worldDir();
×
282
        const std::filesystem::path worldBackupDir = Traintastic::instance->worldBackupDir();
×
283

284
        if(!std::filesystem::is_directory(worldBackupDir))
×
285
        {
286
          std::error_code ec;
×
287
          std::filesystem::create_directories(worldBackupDir, ec);
×
288
          if(ec)
×
289
            Log::log(*this, LogMessage::C1007_CREATING_WORLD_BACKUP_DIRECTORY_FAILED_X, ec);
×
290
        }
291

292
        if(std::filesystem::is_directory(worldDir / uuid.value()))
×
293
        {
294
          std::error_code ec;
×
295
          std::filesystem::rename(worldDir / uuid.value(), worldBackupDir / uuid.value() += dateTimeStr(), ec);
×
296
          if(ec)
×
297
            Log::log(*this, LogMessage::C1006_CREATING_WORLD_BACKUP_FAILED_X, ec);
×
298
        }
299

300
        if(std::filesystem::is_regular_file(worldDir / uuid.value() += dotCTW))
×
301
        {
302
          std::error_code ec;
×
303
          std::filesystem::rename(worldDir / uuid.value() += dotCTW, worldBackupDir / uuid.value() += dateTimeStr() += dotCTW, ec);
×
304
          if(ec)
×
305
            Log::log(*this, LogMessage::C1006_CREATING_WORLD_BACKUP_FAILED_X, ec);
×
306
        }
307

308
        // save world:
309
        std::filesystem::path savePath = worldDir / uuid.value();
×
310
        if(!Traintastic::instance->settings->saveWorldUncompressed)
×
311
          savePath += dotCTW;
×
312

313
        WorldSaver saver(*this, savePath);
×
314

315
        if(Traintastic::instance)
×
316
        {
317
          Traintastic::instance->settings->lastWorld = uuid.value();
×
318
          Traintastic::instance->worldList->update(*this, savePath);
×
319
        }
320

321
        Log::log(*this, LogMessage::N1022_SAVED_WORLD_X, name.value());
×
322
      }
×
323
      catch(const std::exception& e)
×
324
      {
325
        Log::log(*this, LogMessage::C1005_SAVING_WORLD_FAILED_X, e);
×
326
      }
×
327
    }}
×
328
  , getObject_{*this, "get_object", MethodFlags::Internal | MethodFlags::ScriptCallable,
416✔
329
      [this](const std::string& objectId)
208✔
330
      {
331
        return getObjectById(objectId);
15✔
332
      }}
333
  , getLNCVProgrammer{*this, "get_lncv_programmer", MethodFlags::NoScript,
208✔
334
      [](const ObjectPtr& interface) -> std::shared_ptr<LNCVProgrammer>
×
335
      {
336
        if(auto controller = std::dynamic_pointer_cast<LNCVProgrammingController>(interface))
×
337
          return std::make_shared<LNCVProgrammer>(*controller);
×
338
        return {};
×
339
      }}
340
  , onEvent{*this, "on_event", EventFlags::Scriptable}
416✔
341
{
342
  Attributes::addDisplayName(uuid, DisplayName::World::uuid);
208✔
343
  m_interfaceItems.add(uuid);
208✔
344
  Attributes::addDisplayName(name, DisplayName::Object::name);
208✔
345
  m_interfaceItems.add(name);
208✔
346
  Attributes::addEnabled(scale, false);
208✔
347
  Attributes::addValues(scale, WorldScaleValues);
208✔
348
  m_interfaceItems.add(scale);
208✔
349
  Attributes::addEnabled(scaleRatio, false);
208✔
350
  Attributes::addMinMax(scaleRatio, 1., 1000.);
208✔
351
  Attributes::addVisible(scaleRatio, false);
208✔
352
  m_interfaceItems.add(scaleRatio);
208✔
353

354
  m_interfaceItems.add(onlineWhenLoaded);
208✔
355
  m_interfaceItems.add(powerOnWhenLoaded);
208✔
356
  m_interfaceItems.add(runWhenLoaded);
208✔
357

358
  Attributes::addCategory(correctOutputPosWhenLocked, Category::trains);
208✔
359
  Attributes::addEnabled(correctOutputPosWhenLocked, true);
208✔
360
  m_interfaceItems.add(correctOutputPosWhenLocked);
208✔
361

362
  Attributes::addCategory(extOutputChangeAction, Category::trains);
208✔
363
  Attributes::addEnabled(extOutputChangeAction, true);
208✔
364
  Attributes::addValues(extOutputChangeAction, extOutputChangeActionValues);
208✔
365
  m_interfaceItems.add(extOutputChangeAction);
208✔
366

367
  Attributes::addCategory(pathReleaseDelay, Category::trains);
208✔
368
  Attributes::addEnabled(pathReleaseDelay, true);
208✔
369
  Attributes::addMinMax(pathReleaseDelay, {0, 15000}); // Up to 15 seconds
208✔
370
  m_interfaceItems.add(pathReleaseDelay);
208✔
371

372
  // Debug options:
373
  Attributes::addCategory(debugBlockEvents, Category::debug);
208✔
374
  m_interfaceItems.add(debugBlockEvents);
208✔
375
  Attributes::addCategory(debugTrainEvents, Category::debug);
208✔
376
  m_interfaceItems.add(debugTrainEvents);
208✔
377
  Attributes::addCategory(debugZoneEvents, Category::debug);
208✔
378
  m_interfaceItems.add(debugZoneEvents);
208✔
379

380
  Attributes::addObjectEditor(decoderControllers, false);
208✔
381
  m_interfaceItems.add(decoderControllers);
208✔
382
  Attributes::addObjectEditor(inputControllers, false);
208✔
383
  m_interfaceItems.add(inputControllers);
208✔
384
  Attributes::addObjectEditor(outputControllers, false);
208✔
385
  m_interfaceItems.add(outputControllers);
208✔
386
  Attributes::addObjectEditor(identificationControllers, false);
208✔
387
  m_interfaceItems.add(identificationControllers);
208✔
388
  Attributes::addObjectEditor(lncvProgrammingControllers, false);
208✔
389
  m_interfaceItems.add(lncvProgrammingControllers);
208✔
390
  Attributes::addObjectEditor(loconetInterfaces, false);
208✔
391
  m_interfaceItems.add(loconetInterfaces);
208✔
392

393
  Attributes::addObjectEditor(interfaces, false);
208✔
394
  m_interfaceItems.add(interfaces);
208✔
395
  Attributes::addObjectEditor(decoders, false);
208✔
396
  m_interfaceItems.add(decoders);
208✔
397
  Attributes::addObjectEditor(inputs, false);
208✔
398
  m_interfaceItems.add(inputs);
208✔
399
  Attributes::addObjectEditor(outputs, false);
208✔
400
  m_interfaceItems.add(outputs);
208✔
401
  Attributes::addObjectEditor(identifications, false);
208✔
402
  m_interfaceItems.add(identifications);
208✔
403
  Attributes::addObjectEditor(boosters, false);
208✔
404
  m_interfaceItems.add(boosters);
208✔
405
  Attributes::addObjectEditor(throttles, false);
208✔
406
  m_interfaceItems.add(throttles);
208✔
407
  Attributes::addObjectEditor(boards, false);
208✔
408
  m_interfaceItems.add(boards);
208✔
409

410
  Attributes::addObjectEditor(zones, false);
208✔
411
  m_interfaceItems.add(zones);
208✔
412

413
  Attributes::addObjectEditor(clock, false);
208✔
414
  m_interfaceItems.add(clock);
208✔
415
  Attributes::addObjectEditor(trains, false);
208✔
416
  m_interfaceItems.add(trains);
208✔
417
  Attributes::addObjectEditor(railVehicles, false);
208✔
418
  m_interfaceItems.add(railVehicles);
208✔
419
  Attributes::addObjectEditor(luaScripts, false);
208✔
420
  m_interfaceItems.add(luaScripts);
208✔
421

422
  Attributes::addObjectEditor(blockRailTiles, false);
208✔
423
  m_interfaceItems.add(blockRailTiles);
208✔
424

425
  Attributes::addObjectEditor(linkRailTiles, false);
208✔
426
  m_interfaceItems.add(linkRailTiles);
208✔
427
  Attributes::addObjectEditor(nxManager, false);
208✔
428
  m_interfaceItems.add(nxManager);
208✔
429

430
  Attributes::addObjectEditor(statuses, false);
208✔
431
  m_interfaceItems.add(statuses);
208✔
432

433
  Attributes::addObjectEditor(hardwareThrottles, false);
208✔
434
  m_interfaceItems.add(hardwareThrottles);
208✔
435

436
  Attributes::addObjectEditor(state, false);
208✔
437
  m_interfaceItems.add(state);
208✔
438
  Attributes::addObjectEditor(edit, false);
208✔
439
  m_interfaceItems.add(edit);
208✔
440
  Attributes::addObjectEditor(offline, false);
208✔
441
  m_interfaceItems.add(offline);
208✔
442
  Attributes::addObjectEditor(online, false);
208✔
443
  m_interfaceItems.add(online);
208✔
444
  Attributes::addObjectEditor(powerOff, false);
208✔
445
  m_interfaceItems.add(powerOff);
208✔
446
  Attributes::addObjectEditor(powerOn, false);
208✔
447
  m_interfaceItems.add(powerOn);
208✔
448
  Attributes::addObjectEditor(stop, false);
208✔
449
  m_interfaceItems.add(stop);
208✔
450
  Attributes::addObjectEditor(run, false);
208✔
451
  m_interfaceItems.add(run);
208✔
452
  Attributes::addObjectEditor(mute, false);
208✔
453
  m_interfaceItems.add(mute);
208✔
454
  Attributes::addObjectEditor(noSmoke, false);
208✔
455
  m_interfaceItems.add(noSmoke);
208✔
456
  Attributes::addEnabled(simulation, false);
208✔
457
  Attributes::addObjectEditor(simulation, false);
208✔
458
  m_interfaceItems.add(simulation);
208✔
459

460
  m_interfaceItems.add(simulationStatus);
208✔
461

462
  Attributes::addObjectEditor(save, false);
208✔
463
  m_interfaceItems.add(save);
208✔
464

465
  m_interfaceItems.add(getObject_);
208✔
466

467
  Attributes::addObjectEditor(getLNCVProgrammer, false);
208✔
468
  m_interfaceItems.add(getLNCVProgrammer);
208✔
469

470
  m_interfaceItems.add(onEvent);
208✔
471

472
  updateEnabled();
208✔
473
}
208✔
474

475
World::~World()
208✔
476
{
477
  luaScripts->stopAll(); // no surprise event actions during destruction
208✔
478

479
  deleteAll(*interfaces);
208✔
480
  deleteAll(*identifications);
208✔
481
  deleteAll(*boards);
208✔
482
  deleteAll(*zones);
208✔
483
  deleteAll(*throttles);
208✔
484
  deleteAll(*trains);
208✔
485
  deleteAll(*railVehicles);
208✔
486
  deleteAll(*luaScripts);
208✔
487
  luaScripts.setValueInternal(nullptr);
208✔
488
}
208✔
489

490
std::string World::getUniqueId(std::string_view prefix) const
515✔
491
{
492
  std::string uniqueId{prefix};
515✔
493
  uniqueId.append("_");
515✔
494
  uint32_t number = 0;
515✔
495
  do
496
  {
497
    uniqueId.resize(prefix.size() + 1);
659✔
498
    uniqueId.append(std::to_string(++number));
659✔
499
  }
500
  while(isObject(uniqueId));
659✔
501

502
  return uniqueId;
515✔
503
}
×
504

505
bool World::isObject(const std::string& _id) const
659✔
506
{
507
  return m_objects.find(_id) != m_objects.end() || _id == id || _id == Traintastic::id;
659✔
508
}
509

510
ObjectPtr World::getObjectById(const std::string& _id) const
26✔
511
{
512
  auto it = m_objects.find(_id);
26✔
513
  if(it != m_objects.end())
26✔
514
    return it->second.lock();
17✔
515
  if(_id == classId)
9✔
516
    return std::const_pointer_cast<Object>(shared_from_this());
9✔
517
  return ObjectPtr();
×
518
}
519

520
ObjectPtr World::getObjectByPath(std::string_view path) const
11✔
521
{
522
  std::vector<std::string> ids;
11✔
523
  boost::split(ids, path, [](char c){ return c == '.'; });
66✔
524
  auto it = ids.cbegin();
11✔
525

526
  ObjectPtr obj = getObjectById(*it);
11✔
527
  while(obj && ++it != ids.cend())
11✔
528
  {
529
    if(AbstractProperty* property = obj->getProperty(*it); property && property->type() == ValueType::Object)
×
530
      obj = property->toObject();
×
531
    else if(AbstractVectorProperty* vectorProperty = obj->getVectorProperty(*it); vectorProperty && vectorProperty->type() == ValueType::Object)
×
532
    {
533
      obj.reset();
×
534
      const size_t size = vectorProperty->size();
×
535
      for(size_t i = 0; i < size; i++)
×
536
      {
537
        ObjectPtr v = vectorProperty->getObject(i);
×
538
        if(path == v->getObjectId())
×
539
        {
540
          obj = v;
×
541
          it++;
×
542
          break;
×
543
        }
544
      }
×
545
    }
546
    else
547
      obj.reset();
×
548
  }
549
  return obj;
22✔
550
}
11✔
551

552
void World::export_(std::vector<std::byte>& data)
×
553
{
554
  try
555
  {
556
    WorldSaver saver(*this, data);
×
557
    Log::log(*this, LogMessage::N1025_EXPORTED_WORLD_SUCCESSFULLY);
×
558
    //return true;
559
  }
×
560
  catch(const std::exception& e)
×
561
  {
562
    throw LogMessageException(LogMessage::C1010_EXPORTING_WORLD_FAILED_X, e);
×
563
  }
×
564
}
×
565

566
void World::loaded()
3✔
567
{
568
  updateScaleRatio();
3✔
569
  Object::loaded();
3✔
570
}
3✔
571

572
void World::worldEvent(WorldState worldState, WorldEvent worldEvent)
13✔
573
{
574
  Object::worldEvent(worldState, worldEvent);
13✔
575

576
  const bool editState = contains(worldState, WorldState::Edit);
13✔
577
  const bool runState = contains(worldState, WorldState::Run);
13✔
578

579
  Attributes::setEnabled(scale, editState && !runState);
13✔
580
  Attributes::setEnabled(scaleRatio, editState && !runState);
13✔
581

582
  fireEvent(onEvent, worldState, worldEvent);
13✔
583
}
13✔
584

585
void World::event(const WorldEvent value)
13✔
586
{
587
  // Update state:
588
  switch(value)
13✔
589
  {
590
    case WorldEvent::EditDisabled:
1✔
591
      state.setValueInternal(state.value() - WorldState::Edit);
1✔
592
      break;
1✔
593

594
    case WorldEvent::EditEnabled:
1✔
595
      state.setValueInternal(state.value() + WorldState::Edit);
1✔
596
      break;
1✔
597

598
    case WorldEvent::Offline:
×
599
      Log::log(*this, LogMessage::N1013_COMMUNICATION_DISABLED);
×
600
      state.setValueInternal(state.value() - WorldState::Online);
×
601
      break;
×
602

603
    case WorldEvent::Online:
×
604
      Log::log(*this, LogMessage::N1012_COMMUNICATION_ENABLED);
×
605
      state.setValueInternal(state.value() + WorldState::Online);
×
606
      break;
×
607

608
    case WorldEvent::PowerOff:
×
609
      Log::log(*this, LogMessage::N1015_POWER_OFF);
×
610
      state.setValueInternal(state.value() - WorldState::PowerOn - WorldState::Run);
×
611
      break;
×
612

613
    case WorldEvent::PowerOn:
×
614
      Log::log(*this, LogMessage::N1014_POWER_ON);
×
615
      state.setValueInternal(state.value() + WorldState::PowerOn);
×
616
      break;
×
617

618
    case WorldEvent::Stop:
2✔
619
      Log::log(*this, LogMessage::N1017_STOPPED);
2✔
620
      state.setValueInternal(state.value() - WorldState::Run);
2✔
621
      break;
2✔
622

623
    case WorldEvent::Run:
8✔
624
      Log::log(*this, LogMessage::N1016_RUNNING);
8✔
625
      state.setValueInternal(state.value() + WorldState::PowerOn + WorldState::Run);
8✔
626
      break;
8✔
627

628
    case WorldEvent::Unmute:
×
629
      Log::log(*this, LogMessage::N1019_MUTE_DISABLED);
×
630
      state.setValueInternal(state.value() - WorldState::Mute);
×
631
      break;
×
632

633
    case WorldEvent::Mute:
×
634
      Log::log(*this, LogMessage::N1018_MUTE_ENABLED);
×
635
      state.setValueInternal(state.value() + WorldState::Mute);
×
636
      break;
×
637

638
    case WorldEvent::NoSmoke:
×
639
      Log::log(*this, LogMessage::N1021_SMOKE_DISABLED);
×
640
      state.setValueInternal(state.value() + WorldState::NoSmoke);
×
641
      break;
×
642

643
    case WorldEvent::Smoke:
×
644
      Log::log(*this, LogMessage::N1020_SMOKE_ENABLED);
×
645
      state.setValueInternal(state.value() - WorldState::NoSmoke);
×
646
      break;
×
647

648
    case WorldEvent::SimulationDisabled:
×
649
      Log::log(*this, LogMessage::N1023_SIMULATION_DISABLED);
×
650
      state.setValueInternal(state.value() - WorldState::Simulation);
×
651
      break;
×
652

653
    case WorldEvent::SimulationEnabled:
1✔
654
      Log::log(*this, LogMessage::N1024_SIMULATION_ENABLED);
1✔
655
      state.setValueInternal(state.value() + WorldState::Simulation);
1✔
656
      break;
1✔
657
  }
658

659
  updateEnabled();
13✔
660

661
  const WorldState worldState = state;
13✔
662
  worldEvent(worldState, value);
13✔
663
  for(auto& it : m_objects)
140✔
664
    it.second.lock()->worldEvent(worldState, value);
127✔
665
}
13✔
666

667
void World::updateEnabled()
221✔
668
{
669
  const bool isOnline = contains(state.value(), WorldState::Online);
221✔
670
  const bool isPoweredOn = contains(state.value(), WorldState::PowerOn);
221✔
671
  const bool isRunning = contains(state.value(), WorldState::Run);
221✔
672

673
  Attributes::setEnabled(simulation, !isOnline && !isPoweredOn && !isRunning);
221✔
674
}
221✔
675

676
void World::updateScaleRatio()
3✔
677
{
678
  if(scale != WorldScale::Custom)
3✔
679
  {
680
    scaleRatio.setValueInternal(getScaleRatio(scale));
3✔
681
    Attributes::setVisible(scaleRatio, false);
3✔
682
  }
683
  else
684
    Attributes::setVisible(scaleRatio, true);
×
685
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc