• 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

54.4
/server/src/world/worldsaver.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 "worldsaver.hpp"
23
#include <fstream>
24
#include <boost/uuid/uuid_io.hpp>
25
#include <version.hpp>
26
#include "world.hpp"
27
#include "../core/stateobject.hpp"
28
#include "../core/objectproperty.tpp"
29
#include "../status/simulationstatus.hpp"
30
#include "../utils/sha1.hpp"
31
#include "ctwwriter.hpp"
32

33
using nlohmann::json;
34

35
WorldSaver::WorldSaver(const World& world, Options options)
3✔
36
{
37
  m_states = json::object();
3✔
38
  m_data = json::object();
3✔
39
  m_state = json::object();
3✔
40

41
  {
42
    json worldState = json::object();
3✔
43
    world.Object::save(*this, m_data, worldState);
3✔
44
    if(!worldState.empty())
3✔
45
      m_states[world.getObjectId()] = worldState;
×
46
    m_data.erase("class_id");
6✔
47
    // ugly fixup: remove simulation status object
48
    if(auto it = m_data.find(world.statuses.name()); it != m_data.end() && it->is_array()) [[likely]]
3✔
49
    {
50
      const auto removeId = world.simulationStatus->getObjectId();
3✔
51
      for(size_t i = 0; i < it->size(); i++)
4✔
52
      {
53
        if(it->operator[](i) == removeId)
1✔
54
        {
55
          it->erase(i);
×
56
          break;
×
57
        }
58
      }
59
    }
3✔
60
  }
3✔
61

62
  m_data["uuid"] = m_state["uuid"] = world.uuid.value();
3✔
63

64
  m_data["is_auto_save"] = options.isAutoSave;
3✔
65
  m_data["is_export"] = options.isExport;
3✔
66

67
  // traintastic version info:
68
  {
69
    json version;
3✔
70
    version["major"] = TRAINTASTIC_VERSION_MAJOR;
3✔
71
    version["minor"] = TRAINTASTIC_VERSION_MINOR;
3✔
72
    version["patch"] = TRAINTASTIC_VERSION_PATCH;
3✔
73
    version["version"] = TRAINTASTIC_VERSION_FULL;
3✔
74

75
    json traintastic;
3✔
76
    traintastic["version"] = version;
3✔
77

78
    m_data["traintastic"] = m_state["traintastic"] = traintastic;
3✔
79
  }
3✔
80

81
  {
82
    json objects = json::array();
3✔
83
    json stateObjects = json::array();
3✔
84

85
    for(const auto& it : world.m_objects)
9✔
86
    {
87
      if(ObjectPtr object = it.second.lock())
6✔
88
      {
89
        if(auto stateObject = std::dynamic_pointer_cast<StateObject>(object))
6✔
90
        {
91
          json data = saveStateObject(stateObject);
×
92
          if(!data.empty())
×
93
            stateObjects.push_back(std::move(data));
×
94
        }
×
95
        else
96
        {
97
          json data = saveObject(object);
6✔
98
          if(!data.empty())
6✔
99
            objects.push_back(std::move(data));
6✔
100
        }
12✔
101
      }
6✔
102
    }
103

104
    std::sort(objects.begin(), objects.end(),
3✔
105
      [](const json& a, const json& b)
4✔
106
      {
107
        return (a["id"] < b["id"]);
4✔
108
      });
109

110
    m_data["objects"] = objects;
3✔
111
    m_state["objects"] = stateObjects;
3✔
112
    m_state["states"] = m_states;
3✔
113
  }
3✔
114
}
3✔
115

116
WorldSaver::WorldSaver(const World& world, const std::filesystem::path& path, Options options)
3✔
117
  : WorldSaver(world, options)
3✔
118
{
119
  if(path.extension() == World::dotCTW)
3✔
120
  {
121
    CTWWriter ctw(path);
3✔
122
    writeCTW(ctw);
3✔
123
  }
3✔
124
  else
125
  {
126
    saveToDisk(m_data, path / World::filename);
×
127
    saveToDisk(m_state, path / World::filenameState);
×
128
    deleteFiles(path);
×
129
    writeFiles(path);
×
130
  }
131
}
3✔
132

133
WorldSaver::WorldSaver(const World& world, std::vector<std::byte>& memory, Options options)
×
134
  : WorldSaver(world, options)
×
135
{
136
  CTWWriter ctw(memory);
×
137
  writeCTW(ctw);
×
138
}
×
139

140
void WorldSaver::writeCTW(CTWWriter& ctw)
3✔
141
{
142
  ctw.writeFile(World::filename, m_data);
3✔
143
  ctw.writeFile(World::filenameState, m_state);
3✔
144
  for(const auto& file : m_writeFiles)
4✔
145
    ctw.writeFile(file.first, file.second);
1✔
146
}
3✔
147

148
json WorldSaver::saveObject(const ObjectPtr& object)
12✔
149
{
150
  json objectData = json::object();
12✔
151
  json objectState = json::object();
12✔
152

153
  object->save(*this, objectData, objectState);
12✔
154

155
  if(!objectState.empty())
12✔
156
    m_states[object->getObjectId()] = objectState;
7✔
157

158
  return objectData;
24✔
159
}
12✔
160

161
json WorldSaver::saveStateObject(const std::shared_ptr<StateObject>& object)
×
162
{
163
  json objectState = json::object();
×
164
  static_cast<Object&>(*object).save(*this, objectState, objectState);
×
165
  return objectState;
×
166
}
×
167

168
void WorldSaver::deleteFile(std::filesystem::path filename)
×
169
{
170
  m_deleteFiles.emplace_back(std::move(filename));
×
171
}
×
172

173
void WorldSaver::writeFile(std::filesystem::path filename, std::string data)
1✔
174
{
175
  m_writeFiles.push_back({std::move(filename), std::move(data)});
1✔
176
}
1✔
177

178
void WorldSaver::deleteFiles(const std::filesystem::path& basePath)
×
179
{
180
  for(const auto& filename : m_deleteFiles)
×
181
  {
182
    std::error_code ec;
×
183
    std::filesystem::remove(basePath / filename, ec);
×
184
    //! \todo report error if removal fails of existing file
185
  }
186
}
×
187

188
void WorldSaver::writeFiles(const std::filesystem::path& basePath)
×
189
{
190
  for(const auto& file : m_writeFiles)
×
191
    saveToDisk(file.second, basePath / file.first);
×
192
}
×
193

194
void WorldSaver::saveToDisk(const json& data, const std::filesystem::path& filename)
×
195
{
196
  std::filesystem::path dir = std::filesystem::path(filename).remove_filename();
×
197
  std::string s = dir.string();
×
198
  if(!std::filesystem::is_directory(dir))
×
199
    std::filesystem::create_directories(dir);
×
200

201
  std::ofstream file(filename);
×
202
  if(file.is_open())
×
203
  {
204
    file << data.dump(2);
×
205
    //Traintastic::instance->console->notice(classId, "Saved world " + name.value());
206
  }
207
  else
208
    throw std::runtime_error("file not open");
×
209
    //Traintastic::instance->console->critical(classId, "Can't write to world file");
210
}
×
211

212
void WorldSaver::saveToDisk(const std::string& data, const std::filesystem::path& filename)
×
213
{
214
  if(std::filesystem::exists(filename) &&
×
215
      std::filesystem::file_size(filename) == data.size() &&
×
216
      Sha1::of(filename) == Sha1::of(data))
×
217
    return;
×
218

219
  std::filesystem::path dir = std::filesystem::path(filename).remove_filename();
×
220
  std::string s = dir.string();
×
221
  if(!std::filesystem::is_directory(dir))
×
222
    std::filesystem::create_directories(dir);
×
223

224
  std::ofstream file(filename);
×
225
  if(file.is_open())
×
226
  {
227
    file << data;
×
228
    //Traintastic::instance->console->notice(classId, "Saved world " + name.value());
229
  }
230
  else
231
    throw std::runtime_error("file not open");
×
232
    //Traintastic::instance->console->critical(classId, "Can't write to world file");
233
}
×
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