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

traintastic / traintastic / 24939412195

25 Apr 2026 07:59PM UTC coverage: 25.624% (-0.04%) from 25.659%
24939412195

push

github

web-flow
Merge pull request #224 from traintastic/diagnostic-report

Add diagnostic report feature

1 of 46 new or added lines in 4 files covered. (2.17%)

2 existing lines in 2 files now uncovered.

8426 of 32883 relevant lines covered (25.62%)

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

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

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

155
  m_signalSet.async_wait(&Traintastic::signalHandler);
×
156

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

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

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

216
Traintastic::RunStatus Traintastic::run(const std::string& worldUUID, bool simulate, bool online, bool power, bool run)
×
217
{
218
  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));
×
219
  Log::log(*this, LogMessage::I1001_TRAINTASTIC_VX, std::string_view{TRAINTASTIC_VERSION_FULL});
×
220
  Log::log(*this, LogMessage::I1006_X, boostVersion);
×
221
  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)});
×
222
  Log::log(*this, LogMessage::I1008_X, std::string_view{archive_version_details()});
×
223
  Log::log(*this, LogMessage::I1009_ZLIB_X, std::string_view{zlibVersion()});
×
224
  Log::log(*this, LogMessage::I9002_X, Lua::getVersion());
×
225

226
  settings = std::make_shared<Settings>(m_dataDir);
×
227
  Attributes::setEnabled(restart, settings->allowClientServerRestart);
×
228
  Attributes::setEnabled(shutdown, settings->allowClientServerShutdown);
×
229

230
  worldList = std::make_shared<WorldList>(worldDir());
×
231

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

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

257
  if(world)
×
258
  {
259
    if(simulate)
×
260
      world->simulation = true;
×
261

262
    if(online)
×
263
      world->online();
×
264

265
    if(power && run)
×
266
      world->run();
×
267
    else if(power)
×
268
      world->powerOn();
×
269
  }
270

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

281
  return m_restart ? Restart : ExitSuccess;
×
282
}
283

284
void Traintastic::exit()
×
285
{
286
  m_signalSet.cancel();
×
287

288
  if(m_restart)
×
289
    Log::log(*this, LogMessage::N1003_RESTARTING);
×
290
  else
291
    Log::log(*this, LogMessage::N1004_SHUTTING_DOWN);
×
292

293
  if(settings->autoSaveWorldOnExit && world)
×
294
    world->save();
×
295

296
  EventLoop::stop();
×
297
}
×
298

299
void Traintastic::loadWorldUUID(const boost::uuids::uuid& uuid)
×
300
{
301
  if(const WorldList::WorldInfo* info = worldList->find(uuid))
×
302
    loadWorldPath(info->path);
×
303
  else
304
    Log::log(*this, LogMessage::E1002_WORLD_X_DOESNT_EXIST, to_string(uuid));
×
305
}
×
306

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

321
    if(world->onlineWhenLoaded)
×
322
    {
323
      world->online();
×
324
    }
325

326
    if(world->powerOnWhenLoaded)
×
327
    {
328
      if(world->runWhenLoaded)
×
329
      {
330
        world->run();
×
331
      }
332
      else
333
      {
334
        world->powerOn();
×
335
      }
336
    }
337

338
  }
×
339
  catch(const LogMessageException& e)
×
340
  {
341
    Log::log(*this, e.message(), e.args());
×
342
  }
×
343
  catch(const std::exception& e)
×
344
  {
345
    Log::log(*this, LogMessage::C1001_LOADING_WORLD_FAILED_X, e.what());
×
346
  }
×
347
}
×
348

349
void Traintastic::signalHandler(const boost::system::error_code& ec, int signalNumber)
×
350
{
351
  if(ec)
×
352
    return;
×
353

354
#define SIGNAL_NAME_CASE(x) \
355
  {\
356
    case x:\
357
      val = #x;\
358
      break;\
359
  }
360

361
  const char* val = "Unknown signal";
×
362
  switch(signalNumber)
×
363
  {
364
    SIGNAL_NAME_CASE(SIGINT)
×
365
    SIGNAL_NAME_CASE(SIGTERM)
×
366
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
367
    SIGNAL_NAME_CASE(SIGBREAK); //Windows uses SIGBREAK instead of SIGTERM
368
#endif
369
  }
370

371
#undef SIGNAL_NAME_CASE
372

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