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

traintastic / traintastic / 20799539071

07 Jan 2026 11:10PM UTC coverage: 27.22% (+0.07%) from 27.155%
20799539071

push

github

reinder
[world] added scripting feature settings: show/hid scripting related stuff, see #71

53 of 108 new or added lines in 7 files covered. (49.07%)

1 existing line in 1 file now uncovered.

7918 of 29089 relevant lines covered (27.22%)

192.73 hits per line

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

81.25
/server/src/lua/script.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 "script.hpp"
23
#include "scriptlist.hpp"
24
#include "scriptlisttablemodel.hpp"
25
#include "push.hpp"
26
#include "../world/world.hpp"
27
#include <traintastic/enum/worldevent.hpp>
28
#include <traintastic/set/worldstate.hpp>
29
#include "../core/attributes.hpp"
30
#include "../core/method.tpp"
31
#include "../core/objectproperty.tpp"
32
#include "../world/worldloader.hpp"
33
#include "../world/worldsaver.hpp"
34
#include "../utils/displayname.hpp"
35
#include "../log/log.hpp"
36

37
namespace Lua {
38

39
constexpr std::string_view scripts = "scripts";
40
constexpr std::string_view dotLua = ".lua";
41

42
Script::Script(World& world, std::string_view _id) :
73✔
43
  IdObject(world, _id),
44
  m_sandbox{nullptr, nullptr},
73✔
45
  name{this, "name", std::string(_id), PropertyFlags::ReadWrite | PropertyFlags::Store},
146✔
46
  disabled{this, "disabled", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore | PropertyFlags::NoScript,
146✔
47
    [this](bool value)
73✔
48
    {
49
      assert(state != LuaScriptState::Running);
1✔
50
      setState(value ? LuaScriptState::Disabled : LuaScriptState::Stopped);
1✔
51
    }},
1✔
52
  state{this, "state", LuaScriptState::Stopped, PropertyFlags::ReadOnly | PropertyFlags::Store},
73✔
53
  code{this, "code", "", PropertyFlags::ReadWrite | PropertyFlags::NoStore},
146✔
54
  error{this, "error", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore},
146✔
55
  start{*this, "start",
146✔
56
    [this]()
73✔
57
    {
58
      if(state == LuaScriptState::Stopped || state == LuaScriptState::Error)
99✔
59
        startSandbox();
98✔
60
    }},
99✔
61
  stop{*this, "stop",
146✔
62
    [this]()
73✔
63
    {
64
      if(state == LuaScriptState::Running)
166✔
65
        stopSandbox();
93✔
66
    }}
166✔
67
  , clearPersistentVariables{*this, "clear_persistent_variables",
146✔
68
      [this]()
73✔
69
      {
70
        m_persistentVariables = nullptr;
1✔
71
        Log::log(*this, LogMessage::I9003_CLEARED_PERSISTENT_VARIABLES);
1✔
72
        updateEnabled();
1✔
73
      }}
146✔
74
{
75
  Attributes::addDisplayName(name, DisplayName::Object::name);
73✔
76
  Attributes::addEnabled(name, false);
73✔
77
  m_interfaceItems.add(name);
73✔
78
  Attributes::addEnabled(disabled, false);
73✔
79
  m_interfaceItems.add(disabled);
73✔
80
  Attributes::addValues(state, LuaScriptStateValues);
73✔
81
  m_interfaceItems.add(state);
73✔
82
  Attributes::addEnabled(code, false);
73✔
83
  m_interfaceItems.add(code);
73✔
84
  m_interfaceItems.add(error);
73✔
85
  Attributes::addEnabled(start, false);
73✔
86
  m_interfaceItems.add(start);
73✔
87
  Attributes::addEnabled(stop, false);
73✔
88
  m_interfaceItems.add(stop);
73✔
89
  Attributes::addEnabled(clearPersistentVariables, false);
73✔
90
  m_interfaceItems.add(clearPersistentVariables);
73✔
91

92
  updateEnabled();
73✔
93
}
73✔
94

95
void Script::load(WorldLoader& loader, const nlohmann::json& data)
1✔
96
{
97
  IdObject::load(loader, data);
1✔
98

99
  m_basename = id;
1✔
100
  std::string s;
1✔
101
  if(loader.readFile(std::filesystem::path(scripts) / m_basename += dotLua, s))
1✔
102
    code.loadJSON(s);
1✔
103

104
  if(const auto stateData = loader.getState(id); stateData.contains("persistent_variables"))
1✔
105
  {
106
    m_persistentVariables = stateData["persistent_variables"];
1✔
107
  }
1✔
108
}
1✔
109

110
void Script::save(WorldSaver& saver, nlohmann::json& data, nlohmann::json& stateData) const
1✔
111
{
112
  IdObject::save(saver, data, stateData);
1✔
113

114
  if(!m_basename.empty() && m_basename != id.value())
1✔
115
    saver.deleteFile(std::filesystem::path(scripts) / m_basename += dotLua);
×
116

117
  m_basename = id;
1✔
118
  saver.writeFile(std::filesystem::path(scripts) / m_basename += dotLua, code);
1✔
119

120
  if(m_sandbox)
1✔
121
  {
122
    Sandbox::syncPersistentVariables(m_sandbox.get());
×
123
  }
124
  if(!m_persistentVariables.empty())
1✔
125
  {
126
    stateData["persistent_variables"] = m_persistentVariables;
1✔
127
  }
128
}
1✔
129

130
void Script::addToWorld()
73✔
131
{
132
  IdObject::addToWorld();
73✔
133
  m_world.luaScripts->addObject(shared_ptr<Script>());
73✔
134
}
73✔
135

136
void Script::destroying()
73✔
137
{
138
  m_world.luaScripts->removeObject(shared_ptr<Script>());
73✔
139
  IdObject::destroying();
73✔
140
}
73✔
141

142
void Script::loaded()
1✔
143
{
144
  IdObject::loaded();
1✔
145

146
  switch(state)
1✔
147
  {
148
    case LuaScriptState::Error:
×
149
      state.setValueInternal(LuaScriptState::Stopped);
×
150
      break;
×
151

152
    case LuaScriptState::Disabled:
×
153
      disabled.setValueInternal(true);
×
154
      break;
×
155

156
    case LuaScriptState::Running:
×
NEW
157
      if(m_world.feature(WorldFeature::Scripting))
×
158
      {
NEW
159
        startSandbox();
×
NEW
160
        if(state == LuaScriptState::Running)
×
161
        {
NEW
162
          updateEnabled(); // setState doesn't updateEnabled() because the state is already running
×
NEW
163
          auto& running = m_world.luaScripts->status->running;
×
NEW
164
          running.setValueInternal(running + 1); // setState doesn't increment because the state is already running
×
165
        }
166
      }
167
      else
168
      {
NEW
169
        state.setValueInternal(LuaScriptState::Stopped);
×
170
      }
171
      break;
×
172

173
    case LuaScriptState::Stopped:
1✔
174
      break; // nothing to do
1✔
175
  }
176
}
1✔
177

178
void Script::worldEvent(WorldState worldState, WorldEvent worldEvent)
1✔
179
{
180
  IdObject::worldEvent(worldState, worldEvent);
1✔
181

182
  updateEnabled();
1✔
183
}
1✔
184

NEW
185
void Script::worldFeaturesChanged(const WorldFeatures features, WorldFeature changed)
×
186
{
NEW
187
  IdObject::worldFeaturesChanged(features, changed);
×
188

NEW
189
  if(!features[WorldFeature::Scripting] && m_sandbox)
×
190
  {
NEW
191
    stopSandbox();
×
192
  }
193

NEW
194
  updateEnabled();
×
NEW
195
}
×
196

197
void Script::updateEnabled()
267✔
198
{
199
  const bool editable = contains(m_world.state.value(), WorldState::Edit) && state != LuaScriptState::Running;
267✔
200
  const bool stoppedOrError = (state == LuaScriptState::Stopped) || (state == LuaScriptState::Error);
267✔
201

202
  Attributes::setEnabled(id, editable);
267✔
203
  Attributes::setEnabled(name, editable);
267✔
204
  Attributes::setEnabled(disabled, editable);
267✔
205
  Attributes::setEnabled(code, editable);
267✔
206

207
  Attributes::setEnabled(start, stoppedOrError && m_world.feature(WorldFeature::Scripting));
267✔
208
  Attributes::setEnabled(stop, state == LuaScriptState::Running);
267✔
209
  Attributes::setEnabled(clearPersistentVariables, stoppedOrError && !m_persistentVariables.empty());
267✔
210

211
  m_world.luaScripts->updateEnabled();
267✔
212
}
267✔
213

214
void Script::setState(LuaScriptState value)
192✔
215
{
216
  const LuaScriptState oldValue = state;
192✔
217
  if(oldValue == value)
192✔
218
    return;
×
219

220
  state.setValueInternal(value);
192✔
221
  updateEnabled();
192✔
222

223
  // update global lua status:
224
  {
225
    auto& status = m_world.luaScripts->status;
192✔
226

227
    if(oldValue == LuaScriptState::Running) // was running
192✔
228
    {
229
      assert(status->running != 0);
93✔
230
      status->running.setValueInternal(status->running - 1);
93✔
231
    }
232
    if(oldValue == LuaScriptState::Error) // was error
192✔
233
    {
234
      assert(status->error != 0);
×
235
      status->error.setValueInternal(status->error - 1);
×
236
    }
237

238
    if(state == LuaScriptState::Running) // is running
192✔
239
    {
240
      status->running.setValueInternal(status->running + 1);
93✔
241
    }
242
    if(state == LuaScriptState::Error) // is error
192✔
243
    {
244
      status->error.setValueInternal(status->error + 1);
5✔
245
    }
246
  }
247
}
248

249
void Script::startSandbox()
98✔
250
{
251
  assert(!m_sandbox);
98✔
252

253
  if(!m_world.feature(WorldFeature::Scripting))
98✔
254
  {
NEW
255
    return;
×
256
  }
257

258
  if((m_sandbox = Sandbox::create(*this)))
98✔
259
  {
260
    Log::log(*this, LogMessage::N9001_STARTING_SCRIPT);
98✔
261
    lua_State* L = m_sandbox.get();
98✔
262
    const int r = luaL_loadbuffer(L, code.value().c_str(), code.value().size(), "=") || Sandbox::pcall(L, 0, LUA_MULTRET);
98✔
263
    if(r == LUA_OK)
98✔
264
    {
265
      setState(LuaScriptState::Running);
93✔
266
      error.setValueInternal("");
279✔
267
    }
268
    else
269
    {
270
      error.setValueInternal(lua_tostring(L, -1));
10✔
271
      setState(LuaScriptState::Error);
5✔
272
      Log::log(*this, LogMessage::F9002_RUNNING_SCRIPT_FAILED_X, error.value());
5✔
273
      lua_pop(L, 1); // pop error message from the stack
5✔
274
      stopSandbox();
5✔
275
    }
276
  }
277
  else
278
  {
279
    error.setValueInternal("creating lua state failed");
×
280
    setState(LuaScriptState::Error);
×
281
    Log::log(*this, LogMessage::F9001_CREATING_LUA_STATE_FAILED);
×
282
  }
283
}
284

285
void Script::stopSandbox()
98✔
286
{
287
  assert(m_sandbox);
98✔
288
  m_sandbox.reset();
98✔
289
  if(state == LuaScriptState::Running)
98✔
290
  {
291
    setState(LuaScriptState::Stopped);
93✔
292
    Log::log(*this, LogMessage::I9001_STOPPED_SCRIPT);
93✔
293
  }
294
}
98✔
295

296
}
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