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

traintastic / traintastic / 25757282194

12 May 2026 07:28PM UTC coverage: 25.55% (-0.02%) from 25.571%
25757282194

push

github

reinder
[test] fix: new WorldSaver options argument

8428 of 32986 relevant lines covered (25.55%)

176.89 hits per line

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

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

22
#include "world.hpp"
23

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

29
#include "worldsaver.hpp"
30

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

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

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

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

61
#include "../board/board.hpp"
62
#include "../board/boardlist.hpp"
63
#include "../board/list/blockrailtilelist.hpp"
64
#include "../board/list/linkrailtilelist.hpp"
65
#include "../board/nx/nxmanager.hpp"
66
#include "../board/pathfinder/trainpathfinder.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::Node | InputListColumn::Address;
84
constexpr auto outputListColumns = OutputListColumn::Interface | OutputListColumn::Channel | OutputListColumn::Node | 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,688✔
90
{
91
  while(!objectList.empty())
1,947✔
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,688✔
113

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

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

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

145
  world.blockRailTiles.setValueInternal(std::make_shared<BlockRailTileList>(world, world.blockRailTiles.name()));
211✔
146
  world.linkRailTiles.setValueInternal(std::make_shared<LinkRailTileList>(world, world.linkRailTiles.name()));
211✔
147
  world.nxManager.setValueInternal(std::make_shared<NXManager>(world, world.nxManager.name()));
211✔
148
  world.trainPathFinder.setValueInternal(std::make_shared<TrainPathFinder>(world, world.trainPathFinder.name()));
211✔
149

150
  world.simulationStatus.setValueInternal(std::make_shared<SimulationStatus>(world, world.simulationStatus.name()));
211✔
151
}
211✔
152

153
World::World(Private /*unused*/) :
211✔
154
  uuid{this, "uuid", to_string(boost::uuids::random_generator()()), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
155
  name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
422✔
156
  scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](WorldScale /*value*/){ updateScaleRatio(); }},
211✔
157
  scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
211✔
158
  onlineWhenLoaded{this, "online_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
159
  powerOnWhenLoaded{this, "power_on_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
422✔
160
    [this](bool value)
211✔
161
    {
162
      if(!value)
×
163
      {
164
        runWhenLoaded = false; // can't run without power
×
165
      }
166
    }},
×
167
  runWhenLoaded{this, "run_when_loaded", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
422✔
168
    [this](bool value)
211✔
169
    {
170
      if(value)
×
171
      {
172
        powerOnWhenLoaded = true; // can't run without power
×
173
      }
174
    }},
×
175
  correctOutputPosWhenLocked{this, "correct_output_pos_when_locked", true, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
176
  extOutputChangeAction{this, "ext_output_change_action", ExternalOutputChangeAction::EmergencyStopTrain, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
177
  pathReleaseDelay{this, "path_release_delay", 5000, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
178
    featureScripting{this, "feature_scripting", true, PropertyFlags::ReadWrite | PropertyFlags::Store,
422✔
179
    [this](bool value)
211✔
180
    {
181
      setFeature(WorldFeature::Scripting, value);
×
182
    }},
×
183
  debugBlockEvents{this, "debug_block_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
184
  debugTrainEvents{this, "debug_train_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
185
  debugZoneEvents{this, "debug_zone_events", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript},
211✔
186
  decoderControllers{this, "decoder_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
187
  inputControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
188
  outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
189
  identificationControllers{this, "identification_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
190
  lncvProgrammingControllers{this, "lncv_programming_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
191
  cbusInterfaces{this, "cbus_interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
192
  loconetInterfaces{this, "loconet_interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
193
  interfaces{this, "interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
194
  decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
195
  inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
196
  outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
197
  identifications{this, "identifications", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
198
  boosters{this, "boosters", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
199
  boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
200
  zones{this, "zones", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
201
  clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
211✔
202
  throttles{this, "throttles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
203
  trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
204
  railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
205
  luaScripts{this, "lua_scripts", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
206
  blockRailTiles{this, "block_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
207
  linkRailTiles{this, "link_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
208
  nxManager{this, "nx_manager", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
211✔
209
  trainPathFinder{this, "train_path_finder", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
210
  statuses(*this, "statuses", {}, PropertyFlags::ReadOnly | PropertyFlags::Store),
211✔
211
  hardwareThrottles{this, "hardware_throttles", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::NoScript},
211✔
212
  state{this, "state", WorldState(), PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
211✔
213
  edit{this, "edit", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
422✔
214
    [this](bool value)
211✔
215
    {
216
      if(value)
2✔
217
      {
218
        Log::log(*this, LogMessage::N1010_EDIT_MODE_ENABLED);
1✔
219
        state.setValueInternal(state.value() + WorldState::Edit);
1✔
220
        event(WorldEvent::EditEnabled);
1✔
221
      }
222
      else
223
      {
224
        Log::log(*this, LogMessage::N1011_EDIT_MODE_DISABLED);
1✔
225
        state.setValueInternal(state.value() - WorldState::Edit);
1✔
226
        event(WorldEvent::EditDisabled);
1✔
227
      }
228
    }},
2✔
229
  offline{*this, "offline",
422✔
230
    [this]()
211✔
231
    {
232
      event(WorldEvent::Offline);
×
233
    }},
×
234
  online{*this, "online",
422✔
235
    [this]()
211✔
236
    {
237
      event(WorldEvent::Online);
×
238
    }},
×
239
  powerOff{*this, "power_off", MethodFlags::ScriptCallable,
422✔
240
    [this]()
211✔
241
    {
242
      event(WorldEvent::PowerOff);
×
243
    }},
×
244
  powerOn{*this, "power_on",
422✔
245
    [this]()
211✔
246
    {
247
      event(WorldEvent::PowerOn);
×
248
    }},
×
249
  run{*this, "run",
422✔
250
    [this]()
211✔
251
    {
252
      event(WorldEvent::Run);
8✔
253
    }},
8✔
254
  stop{*this, "stop", MethodFlags::ScriptCallable,
422✔
255
    [this]()
211✔
256
    {
257
      event(WorldEvent::Stop);
2✔
258
    }},
2✔
259
  mute{this, "mute", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
422✔
260
    [this](bool value)
211✔
261
    {
262
      event(value ? WorldEvent::Mute : WorldEvent::Unmute);
×
263
    }},
×
264
  noSmoke{this, "no_smoke", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
422✔
265
    [this](bool value)
211✔
266
    {
267
      event(value ? WorldEvent::NoSmoke : WorldEvent::Smoke);
×
268
    }},
×
269
  simulation{this, "simulation", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
422✔
270
    [this](bool value)
211✔
271
    {
272
      simulationStatus->enabled.setValueInternal(value);
1✔
273
      if(value)
1✔
274
      {
275
        statuses.appendInternal(simulationStatus.value());
1✔
276
      }
277
      else
278
      {
279
        statuses.removeInternal(simulationStatus.value());
×
280
      }
281
      event(value ? WorldEvent::SimulationEnabled : WorldEvent::SimulationDisabled);
1✔
282
    }},
1✔
283
  simulationStatus{this, "simulation_status", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::Internal},
211✔
284
  save{*this, "save", MethodFlags::NoScript,
422✔
285
    [this]()
211✔
286
    {
287
      backupAndSave(false);
×
288
      if(Traintastic::instance)
×
289
      {
290
        Traintastic::instance->restartAutoSaveTimer();
×
291
      }
292
    }}
×
293
  , getObject_{*this, "get_object", MethodFlags::Internal | MethodFlags::ScriptCallable,
422✔
294
      [this](const std::string& objectId)
211✔
295
      {
296
        return getObjectById(objectId);
15✔
297
      }}
298
  , getLNCVProgrammer{*this, "get_lncv_programmer", MethodFlags::NoScript,
211✔
299
      [](const ObjectPtr& interface) -> std::shared_ptr<LNCVProgrammer>
×
300
      {
301
        if(auto controller = std::dynamic_pointer_cast<LNCVProgrammingController>(interface))
×
302
          return std::make_shared<LNCVProgrammer>(*controller);
×
303
        return {};
×
304
      }}
305
  , onEvent{*this, "on_event", EventFlags::Scriptable}
422✔
306
{
307
  Attributes::addDisplayName(uuid, DisplayName::World::uuid);
211✔
308
  m_interfaceItems.add(uuid);
211✔
309
  Attributes::addDisplayName(name, DisplayName::Object::name);
211✔
310
  m_interfaceItems.add(name);
211✔
311
  Attributes::addEnabled(scale, false);
211✔
312
  Attributes::addValues(scale, WorldScaleValues);
211✔
313
  m_interfaceItems.add(scale);
211✔
314
  Attributes::addEnabled(scaleRatio, false);
211✔
315
  Attributes::addMinMax(scaleRatio, 1., 1000.);
211✔
316
  Attributes::addVisible(scaleRatio, false);
211✔
317
  m_interfaceItems.add(scaleRatio);
211✔
318

319
  m_interfaceItems.add(onlineWhenLoaded);
211✔
320
  m_interfaceItems.add(powerOnWhenLoaded);
211✔
321
  m_interfaceItems.add(runWhenLoaded);
211✔
322

323
  Attributes::addCategory(correctOutputPosWhenLocked, Category::trains);
211✔
324
  Attributes::addEnabled(correctOutputPosWhenLocked, true);
211✔
325
  m_interfaceItems.add(correctOutputPosWhenLocked);
211✔
326

327
  Attributes::addCategory(extOutputChangeAction, Category::trains);
211✔
328
  Attributes::addEnabled(extOutputChangeAction, true);
211✔
329
  Attributes::addValues(extOutputChangeAction, extOutputChangeActionValues);
211✔
330
  m_interfaceItems.add(extOutputChangeAction);
211✔
331

332
  Attributes::addCategory(pathReleaseDelay, Category::trains);
211✔
333
  Attributes::addEnabled(pathReleaseDelay, true);
211✔
334
  Attributes::addMinMax(pathReleaseDelay, {0, 15000}); // Up to 15 seconds
211✔
335
  m_interfaceItems.add(pathReleaseDelay);
211✔
336

337
  // Features:
338
  Attributes::addCategory(featureScripting, Category::features);
211✔
339
  m_interfaceItems.add(featureScripting);
211✔
340

341
  // Debug options:
342
  Attributes::addCategory(debugBlockEvents, Category::debug);
211✔
343
  m_interfaceItems.add(debugBlockEvents);
211✔
344
  Attributes::addCategory(debugTrainEvents, Category::debug);
211✔
345
  m_interfaceItems.add(debugTrainEvents);
211✔
346
  Attributes::addCategory(debugZoneEvents, Category::debug);
211✔
347
  m_interfaceItems.add(debugZoneEvents);
211✔
348

349
  Attributes::addObjectEditor(decoderControllers, false);
211✔
350
  m_interfaceItems.add(decoderControllers);
211✔
351
  Attributes::addObjectEditor(inputControllers, false);
211✔
352
  m_interfaceItems.add(inputControllers);
211✔
353
  Attributes::addObjectEditor(outputControllers, false);
211✔
354
  m_interfaceItems.add(outputControllers);
211✔
355
  Attributes::addObjectEditor(identificationControllers, false);
211✔
356
  m_interfaceItems.add(identificationControllers);
211✔
357
  Attributes::addObjectEditor(lncvProgrammingControllers, false);
211✔
358
  m_interfaceItems.add(lncvProgrammingControllers);
211✔
359
  Attributes::addObjectEditor(cbusInterfaces, false);
211✔
360
  m_interfaceItems.add(cbusInterfaces);
211✔
361
  Attributes::addObjectEditor(loconetInterfaces, false);
211✔
362
  m_interfaceItems.add(loconetInterfaces);
211✔
363

364
  Attributes::addObjectEditor(interfaces, false);
211✔
365
  m_interfaceItems.add(interfaces);
211✔
366
  Attributes::addObjectEditor(decoders, false);
211✔
367
  m_interfaceItems.add(decoders);
211✔
368
  Attributes::addObjectEditor(inputs, false);
211✔
369
  m_interfaceItems.add(inputs);
211✔
370
  Attributes::addObjectEditor(outputs, false);
211✔
371
  m_interfaceItems.add(outputs);
211✔
372
  Attributes::addObjectEditor(identifications, false);
211✔
373
  m_interfaceItems.add(identifications);
211✔
374
  Attributes::addObjectEditor(boosters, false);
211✔
375
  m_interfaceItems.add(boosters);
211✔
376
  Attributes::addObjectEditor(throttles, false);
211✔
377
  m_interfaceItems.add(throttles);
211✔
378
  Attributes::addObjectEditor(boards, false);
211✔
379
  m_interfaceItems.add(boards);
211✔
380

381
  Attributes::addObjectEditor(zones, false);
211✔
382
  m_interfaceItems.add(zones);
211✔
383

384
  Attributes::addObjectEditor(clock, false);
211✔
385
  m_interfaceItems.add(clock);
211✔
386
  Attributes::addObjectEditor(trains, false);
211✔
387
  m_interfaceItems.add(trains);
211✔
388
  Attributes::addObjectEditor(railVehicles, false);
211✔
389
  m_interfaceItems.add(railVehicles);
211✔
390
  Attributes::addObjectEditor(luaScripts, false);
211✔
391
  Attributes::addVisible(luaScripts, featureScripting);
211✔
392
  m_interfaceItems.add(luaScripts);
211✔
393

394
  Attributes::addObjectEditor(blockRailTiles, false);
211✔
395
  m_interfaceItems.add(blockRailTiles);
211✔
396

397
  Attributes::addObjectEditor(linkRailTiles, false);
211✔
398
  m_interfaceItems.add(linkRailTiles);
211✔
399
  Attributes::addObjectEditor(nxManager, false);
211✔
400
  m_interfaceItems.add(nxManager);
211✔
401
  Attributes::addObjectEditor(trainPathFinder, false);
211✔
402
  m_interfaceItems.add(trainPathFinder);
211✔
403

404
  Attributes::addObjectEditor(statuses, false);
211✔
405
  m_interfaceItems.add(statuses);
211✔
406

407
  Attributes::addObjectEditor(hardwareThrottles, false);
211✔
408
  m_interfaceItems.add(hardwareThrottles);
211✔
409

410
  Attributes::addObjectEditor(state, false);
211✔
411
  m_interfaceItems.add(state);
211✔
412
  Attributes::addObjectEditor(edit, false);
211✔
413
  m_interfaceItems.add(edit);
211✔
414
  Attributes::addObjectEditor(offline, false);
211✔
415
  m_interfaceItems.add(offline);
211✔
416
  Attributes::addObjectEditor(online, false);
211✔
417
  m_interfaceItems.add(online);
211✔
418
  Attributes::addObjectEditor(powerOff, false);
211✔
419
  m_interfaceItems.add(powerOff);
211✔
420
  Attributes::addObjectEditor(powerOn, false);
211✔
421
  m_interfaceItems.add(powerOn);
211✔
422
  Attributes::addObjectEditor(stop, false);
211✔
423
  m_interfaceItems.add(stop);
211✔
424
  Attributes::addObjectEditor(run, false);
211✔
425
  m_interfaceItems.add(run);
211✔
426
  Attributes::addObjectEditor(mute, false);
211✔
427
  m_interfaceItems.add(mute);
211✔
428
  Attributes::addObjectEditor(noSmoke, false);
211✔
429
  m_interfaceItems.add(noSmoke);
211✔
430
  Attributes::addEnabled(simulation, false);
211✔
431
  Attributes::addObjectEditor(simulation, false);
211✔
432
  m_interfaceItems.add(simulation);
211✔
433

434
  m_interfaceItems.add(simulationStatus);
211✔
435

436
  Attributes::addObjectEditor(save, false);
211✔
437
  m_interfaceItems.add(save);
211✔
438

439
  m_interfaceItems.add(getObject_);
211✔
440

441
  Attributes::addObjectEditor(getLNCVProgrammer, false);
211✔
442
  m_interfaceItems.add(getLNCVProgrammer);
211✔
443

444
  m_interfaceItems.add(onEvent);
211✔
445

446
  updateEnabled();
211✔
447
  updateFeatures();
211✔
448
}
211✔
449

450
World::~World()
211✔
451
{
452
  luaScripts->stopAll(); // no surprise event actions during destruction
211✔
453

454
  deleteAll(*interfaces);
211✔
455
  deleteAll(*identifications);
211✔
456
  deleteAll(*boards);
211✔
457
  deleteAll(*zones);
211✔
458
  deleteAll(*throttles);
211✔
459
  deleteAll(*trains);
211✔
460
  deleteAll(*railVehicles);
211✔
461
  deleteAll(*luaScripts);
211✔
462
  luaScripts.setValueInternal(nullptr);
211✔
463
}
211✔
464

465
std::string World::getUniqueId(std::string_view prefix) const
518✔
466
{
467
  std::string uniqueId{prefix};
518✔
468
  uniqueId.append("_");
518✔
469
  uint32_t number = 0;
518✔
470
  do
471
  {
472
    uniqueId.resize(prefix.size() + 1);
662✔
473
    uniqueId.append(std::to_string(++number));
662✔
474
  }
475
  while(isObject(uniqueId));
662✔
476

477
  return uniqueId;
518✔
478
}
×
479

480
bool World::isObject(const std::string& _id) const
662✔
481
{
482
  return m_objects.find(_id) != m_objects.end() || _id == id || _id == Traintastic::id;
662✔
483
}
484

485
ObjectPtr World::getObjectById(const std::string& _id) const
26✔
486
{
487
  auto it = m_objects.find(_id);
26✔
488
  if(it != m_objects.end())
26✔
489
    return it->second.lock();
17✔
490
  if(_id == classId)
9✔
491
    return std::const_pointer_cast<Object>(shared_from_this());
9✔
492
  return ObjectPtr();
×
493
}
494

495
ObjectPtr World::getObjectByPath(std::string_view path) const
11✔
496
{
497
  std::vector<std::string> ids;
11✔
498
  boost::split(ids, path, [](char c){ return c == '.'; });
66✔
499
  auto it = ids.cbegin();
11✔
500

501
  ObjectPtr obj = getObjectById(*it);
11✔
502
  while(obj && ++it != ids.cend())
11✔
503
  {
504
    if(AbstractProperty* property = obj->getProperty(*it); property && property->type() == ValueType::Object)
×
505
      obj = property->toObject();
×
506
    else if(AbstractVectorProperty* vectorProperty = obj->getVectorProperty(*it); vectorProperty && vectorProperty->type() == ValueType::Object)
×
507
    {
508
      obj.reset();
×
509
      const size_t size = vectorProperty->size();
×
510
      for(size_t i = 0; i < size; i++)
×
511
      {
512
        ObjectPtr v = vectorProperty->getObject(i);
×
513
        if(path == v->getObjectId())
×
514
        {
515
          obj = v;
×
516
          it++;
×
517
          break;
×
518
        }
519
      }
×
520
    }
521
    else
522
      obj.reset();
×
523
  }
524
  return obj;
22✔
525
}
11✔
526

527
void World::autoSave()
×
528
{
529
  backupAndSave(true);
×
530
}
×
531

532
void World::export_(std::vector<std::byte>& data)
×
533
{
534
  try
535
  {
536
    WorldSaver saver(*this, data,
537
      WorldSaver::Options{
538
        .isAutoSave = false,
539
        .isExport = true,
540
      });
×
541
    Log::log(*this, LogMessage::N1025_EXPORTED_WORLD_SUCCESSFULLY);
×
542
    //return true;
543
  }
×
544
  catch(const std::exception& e)
×
545
  {
546
    throw LogMessageException(LogMessage::C1010_EXPORTING_WORLD_FAILED_X, e);
×
547
  }
×
548
}
×
549

550
void World::loaded()
3✔
551
{
552
  updateFeatures();
3✔
553
  updateScaleRatio();
3✔
554
  Object::loaded();
3✔
555
}
3✔
556

557
void World::worldEvent(WorldState worldState, WorldEvent worldEvent)
13✔
558
{
559
  Object::worldEvent(worldState, worldEvent);
13✔
560

561
  const bool editState = contains(worldState, WorldState::Edit);
13✔
562
  const bool runState = contains(worldState, WorldState::Run);
13✔
563

564
  Attributes::setEnabled(scale, editState && !runState);
13✔
565
  Attributes::setEnabled(scaleRatio, editState && !runState);
13✔
566

567
  fireEvent(onEvent, worldState, worldEvent);
13✔
568
}
13✔
569

570
void World::worldFeaturesChanged(const WorldFeatures features, WorldFeature changed)
4✔
571
{
572
  Object::worldFeaturesChanged(features, changed);
4✔
573

574
  Attributes::setVisible(luaScripts, features[WorldFeature::Scripting]);
4✔
575
}
4✔
576

577
void World::event(const WorldEvent value)
13✔
578
{
579
  // Update state:
580
  switch(value)
13✔
581
  {
582
    case WorldEvent::EditDisabled:
1✔
583
      state.setValueInternal(state.value() - WorldState::Edit);
1✔
584
      break;
1✔
585

586
    case WorldEvent::EditEnabled:
1✔
587
      state.setValueInternal(state.value() + WorldState::Edit);
1✔
588
      break;
1✔
589

590
    case WorldEvent::Offline:
×
591
      Log::log(*this, LogMessage::N1013_COMMUNICATION_DISABLED);
×
592
      state.setValueInternal(state.value() - WorldState::Online);
×
593
      break;
×
594

595
    case WorldEvent::Online:
×
596
      Log::log(*this, LogMessage::N1012_COMMUNICATION_ENABLED);
×
597
      state.setValueInternal(state.value() + WorldState::Online);
×
598
      break;
×
599

600
    case WorldEvent::PowerOff:
×
601
      Log::log(*this, LogMessage::N1015_POWER_OFF);
×
602
      state.setValueInternal(state.value() - WorldState::PowerOn - WorldState::Run);
×
603
      break;
×
604

605
    case WorldEvent::PowerOn:
×
606
      Log::log(*this, LogMessage::N1014_POWER_ON);
×
607
      state.setValueInternal(state.value() + WorldState::PowerOn);
×
608
      break;
×
609

610
    case WorldEvent::Stop:
2✔
611
      Log::log(*this, LogMessage::N1017_STOPPED);
2✔
612
      state.setValueInternal(state.value() - WorldState::Run);
2✔
613
      break;
2✔
614

615
    case WorldEvent::Run:
8✔
616
      Log::log(*this, LogMessage::N1016_RUNNING);
8✔
617
      state.setValueInternal(state.value() + WorldState::PowerOn + WorldState::Run);
8✔
618
      break;
8✔
619

620
    case WorldEvent::Unmute:
×
621
      Log::log(*this, LogMessage::N1019_MUTE_DISABLED);
×
622
      state.setValueInternal(state.value() - WorldState::Mute);
×
623
      break;
×
624

625
    case WorldEvent::Mute:
×
626
      Log::log(*this, LogMessage::N1018_MUTE_ENABLED);
×
627
      state.setValueInternal(state.value() + WorldState::Mute);
×
628
      break;
×
629

630
    case WorldEvent::NoSmoke:
×
631
      Log::log(*this, LogMessage::N1021_SMOKE_DISABLED);
×
632
      state.setValueInternal(state.value() + WorldState::NoSmoke);
×
633
      break;
×
634

635
    case WorldEvent::Smoke:
×
636
      Log::log(*this, LogMessage::N1020_SMOKE_ENABLED);
×
637
      state.setValueInternal(state.value() - WorldState::NoSmoke);
×
638
      break;
×
639

640
    case WorldEvent::SimulationDisabled:
×
641
      Log::log(*this, LogMessage::N1023_SIMULATION_DISABLED);
×
642
      state.setValueInternal(state.value() - WorldState::Simulation);
×
643
      break;
×
644

645
    case WorldEvent::SimulationEnabled:
1✔
646
      Log::log(*this, LogMessage::N1024_SIMULATION_ENABLED);
1✔
647
      state.setValueInternal(state.value() + WorldState::Simulation);
1✔
648
      break;
1✔
649
  }
650

651
  updateEnabled();
13✔
652

653
  const WorldState worldState = state;
13✔
654
  worldEvent(worldState, value);
13✔
655
  for(auto& it : m_objects)
140✔
656
    it.second.lock()->worldEvent(worldState, value);
127✔
657
}
13✔
658

659
void World::setFeature(WorldFeature feature, bool value)
4✔
660
{
661
  if(m_features[feature] != value)
4✔
662
  {
663
    m_features.set(feature, value);
4✔
664

665
    worldFeaturesChanged(m_features, feature);
4✔
666
    for(auto& it : m_objects)
8✔
667
    {
668
      it.second.lock()->worldFeaturesChanged(m_features, feature);
4✔
669
    }
670
  }
671
}
4✔
672

673
void World::backupAndSave(bool isAutoSave)
×
674
{
675
  try
676
  {
677
    // backup world:
678
    const std::filesystem::path worldDir = Traintastic::instance->worldDir();
×
679
    const std::filesystem::path worldBackupDir = Traintastic::instance->worldBackupDir();
×
680

681
    if(!std::filesystem::is_directory(worldBackupDir))
×
682
    {
683
      std::error_code ec;
×
684
      std::filesystem::create_directories(worldBackupDir, ec);
×
685
      if(ec)
×
686
        Log::log(*this, LogMessage::C1007_CREATING_WORLD_BACKUP_DIRECTORY_FAILED_X, ec);
×
687
    }
688

689
    if(std::filesystem::is_directory(worldDir / uuid.value()))
×
690
    {
691
      std::error_code ec;
×
692
      std::filesystem::rename(worldDir / uuid.value(), worldBackupDir / uuid.value() += dateTimeStr(), ec);
×
693
      if(ec)
×
694
        Log::log(*this, LogMessage::C1006_CREATING_WORLD_BACKUP_FAILED_X, ec);
×
695
    }
696

697
    if(std::filesystem::is_regular_file(worldDir / uuid.value() += dotCTW))
×
698
    {
699
      std::error_code ec;
×
700
      std::filesystem::rename(worldDir / uuid.value() += dotCTW, worldBackupDir / uuid.value() += dateTimeStr() += dotCTW, ec);
×
701
      if(ec)
×
702
        Log::log(*this, LogMessage::C1006_CREATING_WORLD_BACKUP_FAILED_X, ec);
×
703
    }
704

705
    // save world:
706
    std::filesystem::path savePath = worldDir / uuid.value();
×
707
    if(!Traintastic::instance->settings->saveWorldUncompressed)
×
708
      savePath += dotCTW;
×
709

710
    WorldSaver saver(*this, savePath,
711
      WorldSaver::Options{
712
        .isAutoSave = isAutoSave,
713
        .isExport = false,
714
      });
×
715

716
    if(Traintastic::instance)
×
717
    {
718
      Traintastic::instance->settings->lastWorld = uuid.value();
×
719
      Traintastic::instance->worldList->update(*this, savePath);
×
720
    }
721

722
    Log::log(*this, isAutoSave ? LogMessage::I1010_AUTO_SAVED_WORLD_X : LogMessage::N1022_SAVED_WORLD_X, name.value());
×
723
  }
×
724
  catch(const std::exception& e)
×
725
  {
726
    Log::log(*this, LogMessage::C1005_SAVING_WORLD_FAILED_X, e);
×
727
  }
×
728
}
×
729

730
void World::updateEnabled()
224✔
731
{
732
  const bool isOnline = contains(state.value(), WorldState::Online);
224✔
733
  const bool isPoweredOn = contains(state.value(), WorldState::PowerOn);
224✔
734
  const bool isRunning = contains(state.value(), WorldState::Run);
224✔
735

736
  Attributes::setEnabled(simulation, !isOnline && !isPoweredOn && !isRunning);
224✔
737
}
224✔
738

739
void World::updateFeatures()
214✔
740
{
741
  m_features.set(WorldFeature::Scripting, featureScripting);
214✔
742
  Attributes::setVisible(luaScripts, feature(WorldFeature::Scripting));
214✔
743
}
214✔
744

745
void World::updateScaleRatio()
3✔
746
{
747
  if(scale != WorldScale::Custom)
3✔
748
  {
749
    scaleRatio.setValueInternal(getScaleRatio(scale));
3✔
750
    Attributes::setVisible(scaleRatio, false);
3✔
751
  }
752
  else
753
    Attributes::setVisible(scaleRatio, true);
×
754
}
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