• 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

0.0
/server/src/traintastic/traintastic.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 "traintastic.hpp"
23
#include <fstream>
24
#include <boost/uuid/nil_generator.hpp>
25
#include <boost/uuid/string_generator.hpp>
26
#include <boost/uuid/uuid_io.hpp>
27
#include <archive.h>
28
#include <zlib.h>
29
#include <version.hpp>
30
#include <traintastic/copyright.hpp>
31
#include <traintastic/os/systeminfo.hpp>
32
#include <traintastic/utils/str.hpp>
33
#include "../compat/stdformat.hpp"
34
#include "../core/eventloop.hpp"
35
#include "../network/server.hpp"
36
#include "../core/attributes.hpp"
37
#include "../core/method.tpp"
38
#include "../core/objectproperty.tpp"
39
#include "../world/world.hpp"
40
#include "../world/worldlist.hpp"
41
#include "../world/worldloader.hpp"
42
#include "../log/log.hpp"
43
#include "../log/logmessageexception.hpp"
44
#include "../lua/getversion.hpp"
45

46
using nlohmann::json;
47

48
constexpr std::string_view versionCopyrightAndLicense{
49
  "<h2>Traintastic v" TRAINTASTIC_VERSION " <small>"
50
#ifdef TRAINTASTIC_VERSION_EXTRA_NODASH
51
  TRAINTASTIC_VERSION_EXTRA_NODASH
52
#else
53
  TRAINTASTIC_CODENAME
54
#endif
55
  "</small></h2>"
56
  "<p>" TRAINTASTIC_COPYRIGHT "</p>"
57
  "<p>This program is free software; you can redistribute it and/or"
58
  " modify it under the terms of the GNU General Public License"
59
  " as published by the Free Software Foundation; either version 2"
60
  " of the License, or (at your option) any later version.</p>"
61
  "<p>This program is distributed in the hope that it will be useful,"
62
  " but WITHOUT ANY WARRANTY; without even the implied warranty of"
63
  " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"
64
  " GNU General Public License for more details.</p>"
65
  "<p><a href=\"https://traintastic.org\">traintastic.org</a></p>"
66
};
67

68
std::shared_ptr<Traintastic> Traintastic::instance;
69

70
Traintastic::Traintastic(const std::filesystem::path& dataDir) :
×
71
  m_restart{false},
×
72
  m_dataDir{std::filesystem::absolute(dataDir)},
×
73
  m_signalSet(EventLoop::ioContext()),
×
74
  m_autoSaveTimer(EventLoop::ioContext()),
×
75
  about{this, "about", std::string(versionCopyrightAndLicense), PropertyFlags::ReadOnly},
×
76
  settings{this, "settings", nullptr, PropertyFlags::ReadWrite/*ReadOnly*/},
×
77
  version{this, "version", TRAINTASTIC_VERSION_FULL, PropertyFlags::ReadOnly},
×
78
  world{this, "world", nullptr, PropertyFlags::ReadWrite,
×
79
    [this](const std::shared_ptr<World>& /*newWorld*/)
×
80
    {
81
      if(world)
×
82
        world->destroy();
×
83
      return true;
×
84
    }},
85
  worldList{this, "world_list", nullptr, PropertyFlags::ReadWrite/*ReadOnly*/},
×
86
  newWorld{*this, "new_world",
×
87
    [this]()
×
88
    {
89
#ifndef NDEBUG
90
      std::weak_ptr<World> weakWorld = world.value();
×
91
#endif
92
      world = World::create();
×
93
#ifndef NDEBUG
94
      assert(weakWorld.expired());
×
95
#endif
96
      Log::log(*this, LogMessage::N1002_CREATED_NEW_WORLD);
×
97
      world->edit = true;
×
98
      settings->lastWorld = "";
×
99
      restartAutoSaveTimer();
×
100
    }},
×
101
  loadWorld{*this, "load_world",
×
102
    [this](const std::string& _uuid)
×
103
    {
104
      boost::uuids::uuid uuid;
105
      try
106
      {
107
        uuid = boost::uuids::string_generator()(_uuid);
×
108
      }
109
      catch(const std::exception&)
×
110
      {
111
        uuid = boost::uuids::nil_generator()();
×
112
        Log::log(*this, LogMessage::E1001_INVALID_WORLD_UUID_X, _uuid);
×
113
      }
×
114

115
      if(!uuid.is_nil())
×
116
        loadWorldUUID(uuid);
×
117
    }},
×
118
  closeWorld{*this, "close_world",
×
119
    [this]()
×
120
    {
121
#ifndef NDEBUG
122
      std::weak_ptr<World> weakWorld = world.value();
×
123
#endif
124
      world = nullptr;
×
125
#ifndef NDEBUG
126
      assert(weakWorld.expired());
×
127
#endif
128
      settings->lastWorld = "";
×
129
      Log::log(*this, LogMessage::N1028_CLOSED_WORLD);
×
130
      m_autoSaveTimer.cancel();
×
131
    }},
×
132
  restart{*this, "restart",
×
133
    [this]()
×
134
    {
135
      if(Attributes::getEnabled(restart))
×
136
      {
137
        m_restart = true;
×
138
        exit();
×
139
      }
140
    }},
×
141
  shutdown{*this, "shutdown",
×
142
    [this]()
×
143
    {
144
      if(Attributes::getEnabled(shutdown))
×
145
        exit();
×
146
    }}
×
147
{
148
  if(!std::filesystem::is_directory(m_dataDir))
×
149
    std::filesystem::create_directories(m_dataDir);
×
150

151
  //Register signal handlers to shutdown gracefully
152
  m_signalSet.add(SIGINT);
×
153
  m_signalSet.add(SIGTERM);
×
154
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
155
  m_signalSet.add(SIGBREAK); //Windows uses SIGBREAK instead of SIGTERM
156
#endif
157

158
  m_signalSet.async_wait(&Traintastic::signalHandler);
×
159

160
  m_interfaceItems.add(about);
×
161
  m_interfaceItems.add(settings);
×
162
  m_interfaceItems.add(version);
×
163
  m_interfaceItems.add(world);
×
164
  m_interfaceItems.add(worldList);
×
165
  m_interfaceItems.add(newWorld);
×
166
  m_interfaceItems.add(loadWorld);
×
167
  m_interfaceItems.add(closeWorld);
×
168
  Attributes::addEnabled(restart, false);
×
169
  m_interfaceItems.add(restart);
×
170
  Attributes::addEnabled(shutdown, false);
×
171
  m_interfaceItems.add(shutdown);
×
172
}
×
173

174
std::string Traintastic::getInfo()
×
175
{
176
  std::string info("### Traintastic Server ###\nVersion: " TRAINTASTIC_VERSION_FULL "\n\n");
×
177
  info.append(getSystemInfo());
×
178
  info.append(
×
179
    std::format(
×
180
      "\n"
181
      "### Libraries ###\n"
182
      "boost: {}.{}.{}\n"
183
      "nlohmann::json: {}.{}.{}\n"
184
      "libarchive: {}\n"
185
      "zlib: {}\n"
186
      "lua: {}\n",
187
      BOOST_VERSION / 100000, BOOST_VERSION / 100 % 100, BOOST_VERSION % 100,
×
188
      NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH,
×
189
      archive_version_details(),
×
190
      zlibVersion(),
×
191
      Lua::getVersion()
×
192
    ));
193
  return info;
×
194
}
×
195

196
void Traintastic::importWorld(const std::vector<std::byte>& worldData)
×
197
{
198
  try
199
  {
200
#ifndef NDEBUG
201
    std::weak_ptr<World> weakWorld = world.value();
×
202
#endif
203
    world = WorldLoader(worldData).world();
×
204
#ifndef NDEBUG
205
    assert(weakWorld.expired());
×
206
#endif
207
    Log::log(*this, LogMessage::N1026_IMPORTED_WORLD_SUCCESSFULLY);
×
208
  }
×
209
  catch(const LogMessageException& e)
×
210
  {
211
    throw e;
×
212
  }
×
213
  catch(const std::exception& e)
×
214
  {
215
    throw LogMessageException(LogMessage::C1011_IMPORTING_WORLD_FAILED_X, e.what());
×
216
  }
×
217
}
×
218

219
Traintastic::RunStatus Traintastic::run(const std::string& worldUUID, bool simulate, bool online, bool power, bool run)
×
220
{
221
  static const std::string boostVersion = std::string("boost ").append(std::to_string(BOOST_VERSION / 100000)).append(".").append(std::to_string(BOOST_VERSION / 100 % 100)).append(".").append(std::to_string(BOOST_VERSION % 100));
×
222
  Log::log(*this, LogMessage::I1001_TRAINTASTIC_VX, std::string_view{TRAINTASTIC_VERSION_FULL});
×
223
  Log::log(*this, LogMessage::I1006_X, boostVersion);
×
224
  Log::log(*this, LogMessage::I1007_X, std::string_view{"nlohmann::json " STR(NLOHMANN_JSON_VERSION_MAJOR) "." STR(NLOHMANN_JSON_VERSION_MINOR) "." STR(NLOHMANN_JSON_VERSION_PATCH)});
×
225
  Log::log(*this, LogMessage::I1008_X, std::string_view{archive_version_details()});
×
226
  Log::log(*this, LogMessage::I1009_ZLIB_X, std::string_view{zlibVersion()});
×
227
  Log::log(*this, LogMessage::I9002_X, Lua::getVersion());
×
228

229
  settings = std::make_shared<Settings>(m_dataDir);
×
230
  Attributes::setEnabled(restart, settings->allowClientServerRestart);
×
231
  Attributes::setEnabled(shutdown, settings->allowClientServerShutdown);
×
232

233
  worldList = std::make_shared<WorldList>(worldDir());
×
234

235
  if(!worldUUID.empty())
×
236
  {
237
    loadWorld(worldUUID);
×
238
    if(!world)
×
239
      return ExitFailure;
×
240
  }
241
  else if(settings->loadLastWorldOnStartup && !settings->lastWorld.value().empty())
×
242
    loadWorld(settings->lastWorld.value());
×
243

244
  try
245
  {
246
    m_server = std::make_shared<Server>(
×
247
#ifndef NO_LOCALHOST_ONLY_SETTING
248
      settings->localhostOnly,
×
249
#else
250
      false,
251
#endif
252
      settings->port, settings->discoverable);
×
253
  }
254
  catch(const LogMessageException& e)
×
255
  {
256
    Log::log(Server::id, e.message(), e.args());
×
257
    return ExitFailure;
×
258
  }
×
259

260
  if(world)
×
261
  {
262
    if(simulate)
×
263
      world->simulation = true;
×
264

265
    if(online)
×
266
      world->online();
×
267

268
    if(power && run)
×
269
      world->run();
×
270
    else if(power)
×
271
      world->powerOn();
×
272
  }
273

274
  try
275
  {
276
    EventLoop::exec();
×
277
  }
278
  catch(const std::exception& e)
×
279
  {
280
    Log::log(id, LogMessage::F1008_EVENTLOOP_CRASHED_X, e.what());
×
281
    return ExitFailure;
×
282
  }
×
283

284
  return m_restart ? Restart : ExitSuccess;
×
285
}
286

287
void Traintastic::exit()
×
288
{
289
  m_signalSet.cancel();
×
290
  m_autoSaveTimer.cancel();
×
291

292
  if(m_restart)
×
293
    Log::log(*this, LogMessage::N1003_RESTARTING);
×
294
  else
295
    Log::log(*this, LogMessage::N1004_SHUTTING_DOWN);
×
296

297
  if(settings->autoSaveWorldOnExit && world)
×
298
  {
299
    world->autoSave();
×
300
  }
301

302
  EventLoop::stop();
×
303
}
×
304

305
void Traintastic::loadWorldUUID(const boost::uuids::uuid& uuid)
×
306
{
307
  if(const WorldList::WorldInfo* info = worldList->find(uuid))
×
308
    loadWorldPath(info->path);
×
309
  else
310
    Log::log(*this, LogMessage::E1002_WORLD_X_DOESNT_EXIST, to_string(uuid));
×
311
}
×
312

313
void Traintastic::loadWorldPath(const std::filesystem::path& path)
×
314
{
315
  try
316
  {
317
#ifndef NDEBUG
318
    std::weak_ptr<World> weakWorld = world.value();
×
319
#endif
320
    world = WorldLoader(path).world();
×
321
#ifndef NDEBUG
322
    assert(weakWorld.expired());
×
323
#endif
324
    settings->lastWorld = world->uuid.value();
×
325
    Log::log(*this, LogMessage::N1027_LOADED_WORLD_X, world->name.value());
×
326

327
    if(world->onlineWhenLoaded)
×
328
    {
329
      world->online();
×
330
    }
331

332
    if(world->powerOnWhenLoaded)
×
333
    {
334
      if(world->runWhenLoaded)
×
335
      {
336
        world->run();
×
337
      }
338
      else
339
      {
340
        world->powerOn();
×
341
      }
342
    }
343

344
    restartAutoSaveTimer();
×
345
  }
×
346
  catch(const LogMessageException& e)
×
347
  {
348
    Log::log(*this, e.message(), e.args());
×
349
  }
×
350
  catch(const std::exception& e)
×
351
  {
352
    Log::log(*this, LogMessage::C1001_LOADING_WORLD_FAILED_X, e.what());
×
353
  }
×
354
}
×
355

356
void Traintastic::restartAutoSaveTimer()
×
357
{
358
  m_autoSaveTimer.cancel();
×
359

360
  if(settings->autoSaveInterval != Settings::autoSaveIntervalOff && world)
×
361
  {
362
    m_autoSaveTimer.expires_after(std::chrono::minutes(settings->autoSaveInterval));
×
363
    m_autoSaveTimer.async_wait(
×
364
      [this](std::error_code ec)
×
365
      {
366
        if(!ec && world)
×
367
        {
368
          world->autoSave();
×
369
          restartAutoSaveTimer();
×
370
        }
371
      });
×
372
  }
373
}
×
374

375
void Traintastic::signalHandler(const boost::system::error_code& ec, int signalNumber)
×
376
{
377
  if(ec)
×
378
    return;
×
379

380
#define SIGNAL_NAME_CASE(x) \
381
  {\
382
    case x:\
383
      val = #x;\
384
      break;\
385
  }
386

387
  const char* val = "Unknown signal";
×
388
  switch(signalNumber)
×
389
  {
390
    SIGNAL_NAME_CASE(SIGINT)
×
391
    SIGNAL_NAME_CASE(SIGTERM)
×
392
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
393
    SIGNAL_NAME_CASE(SIGBREAK); //Windows uses SIGBREAK instead of SIGTERM
394
#endif
395
  }
396

397
#undef SIGNAL_NAME_CASE
398

399
  Log::log(*Traintastic::instance, LogMessage::N1001_RECEIVED_SIGNAL_X, std::string_view{val});
×
400
  instance->exit();
×
401
}
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