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

traintastic / traintastic / 21884540759

10 Feb 2026 10:16PM UTC coverage: 27.967% (-0.02%) from 27.984%
21884540759

push

github

reinder
[network] Added CallMethod command to call methods by <object_path>.<method_name>

0 of 84 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

8159 of 29174 relevant lines covered (27.97%)

193.65 hits per line

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

0.0
/server/src/network/session.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 "session.hpp"
23
#include <boost/algorithm/string.hpp>
24
#include <boost/uuid/random_generator.hpp>
25
#include "../traintastic/traintastic.hpp"
26
#include "clientconnection.hpp"
27
#include <traintastic/enum/interfaceitemtype.hpp>
28
#include <traintastic/enum/attributetype.hpp>
29
#ifndef NDEBUG
30
  #include "../core/eventloop.hpp" // for: isEventLoopThread()
31
#endif
32
#include "../core/abstractobjectlist.hpp"
33
#include "../core/abstractunitproperty.hpp"
34
#include "../core/objectproperty.tpp"
35
#include "../core/tablemodel.hpp"
36
#include "../log/log.hpp"
37
#include "../log/logmessageexception.hpp"
38
#include "../log/memorylogger.hpp"
39
#include "../board/board.hpp"
40
#include "../board/tile/tiles.hpp"
41
#include "../hardware/input/monitor/inputmonitor.hpp"
42
#include "../hardware/output/keyboard/outputkeyboard.hpp"
43
#include "../throttle/clientthrottle.hpp"
44

45
#ifdef GetObject
46
  #undef GetObject // GetObject is defined by a winapi header
47
#endif
48

49
namespace {
50

NEW
51
std::pair<std::string_view, std::string_view> splitOnLastDot(std::string_view sv)
×
52
{
NEW
53
  if(const auto pos = sv.rfind('.'); pos != std::string_view::npos)
×
54
  {
NEW
55
    return {sv.substr(0, pos), sv.substr(pos + 1)};
×
56
  }
NEW
57
  return {{}, sv};
×
58
}
59

60
}
61

62
Session::Session(const std::shared_ptr<ClientConnection>& connection) :
×
63
  m_connection{connection},
×
64
  m_uuid{boost::uuids::random_generator()()}
×
65
{
66
  assert(isEventLoopThread());
×
67
}
×
68

69
Session::~Session()
×
70
{
71
  assert(isEventLoopThread());
×
72

73
  m_objectSignals.clear(); // disconnect all, we don't want m_handles modified during the loop
×
74
  for(const auto& it : m_handles)
×
75
  {
76
    if(it.second && isSessionObject(it.second))
×
77
    {
78
      it.second->destroy();
×
79
    }
80
  }
81
}
×
82

83
bool Session::processMessage(const Message& message)
×
84
{
85
  switch(message.command())
×
86
  {
87
    case Message::Command::GetObject:
×
88
    {
89
      std::string id;
×
90
      message.read(id);
×
91

92
      std::vector<std::string> ids;
×
93
      boost::split(ids, id, [](char c){ return c == '.'; });
×
94
      auto it = ids.cbegin();
×
95

96
      ObjectPtr obj;
×
97
      if(*it == Traintastic::classId)
×
98
        obj = Traintastic::instance;
×
99
      else if(Traintastic::instance->world)
×
100
        obj = Traintastic::instance->world->getObjectById(*it);
×
101

102
      while(obj && ++it != ids.cend())
×
103
      {
104
        if(AbstractProperty* property = obj->getProperty(*it); property && property->type() == ValueType::Object)
×
105
          obj = property->toObject();
×
106
        else if(AbstractVectorProperty* vectorProperty = obj->getVectorProperty(*it); vectorProperty && vectorProperty->type() == ValueType::Object)
×
107
        {
108
          obj = nullptr;
×
109
          const size_t size = vectorProperty->size();
×
110
          for(size_t i = 0; i < size; i++)
×
111
          {
112
            ObjectPtr v = vectorProperty->getObject(i);
×
113
            if(id == v->getObjectId())
×
114
            {
115
              obj = v;
×
116
              it++;
×
117
              break;
×
118
            }
119
          }
×
120
        }
121
        else
122
          obj = nullptr;
×
123
      }
124

125
      if(obj)
×
126
      {
127
        auto response = Message::newResponse(message.command(), message.requestId());
×
128
        writeObject(*response, obj);
×
129
        m_connection->sendMessage(std::move(response));
×
130
      }
×
131
      else
132
      {
133
        m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT));
×
134
      }
135
      return true;
×
136
    }
×
137
    case Message::Command::ReleaseObject:
×
138
    {
139
      // client counter value must match server counter value,
140
      // to make sure no handles are "on the wire"
141
      //
142
      const auto handle = message.read<Handle>();
×
143
      const auto counter = message.read<uint32_t>();
×
144
      if(counter == m_handles.getCounter(handle))
×
145
      {
146
        if(auto object = m_handles.getItem(handle); object && isSessionObject(object))
×
147
        {
148
          object->destroy();
×
149
        }
×
150

151
        m_handles.removeHandle(handle);
×
152

153
        auto it = m_objectSignals.find(handle);
×
154
        while(it != m_objectSignals.end())
×
155
        {
156
          it->second.disconnect();
×
157
          m_objectSignals.erase(it);
×
158
          it = m_objectSignals.find(handle);
×
159
        }
160

161
        auto event = Message::newEvent(message.command(), sizeof(Handle));
×
162
        event->write(handle);
×
163
        m_connection->sendMessage(std::move(event));
×
164
      }
×
165
      break;
×
166
    }
167
    case Message::Command::ObjectSetProperty:
×
168
    {
169
      if(message.isRequest() || message.isEvent())
×
170
      {
171
        if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
172
        {
173
          if(AbstractProperty* property = object->getProperty(message.read<std::string>()); property && !property->isInternal())
×
174
          {
175
            try
176
            {
177
              switch(message.read<ValueType>())
×
178
              {
179
                case ValueType::Boolean:
×
180
                  property->fromBool(message.read<bool>());
×
181
                  break;
×
182

183
                case ValueType::Integer:
×
184
                  property->fromInt64(message.read<int64_t>());
×
185
                  break;
×
186

187
                case ValueType::Float:
×
188
                  property->fromDouble(message.read<double>());
×
189
                  break;
×
190

191
                case ValueType::String:
×
192
                  property->fromString(message.read<std::string>());
×
193
                  break;
×
194

195
                default:
×
196
                  throw std::runtime_error("invalid value type");
×
197
              }
198
            }
199
            catch(const std::exception& e) // set property failed
×
200
            {
201
              if(message.isRequest()) // send error response
×
202
              {
203
                m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1018_EXCEPTION_X, e.what()));
×
204
              }
205
              else // send changed event with current value:
206
                objectPropertyChanged(*property);
×
207
            }
×
208

209
            if(message.isRequest()) // send success response
×
210
              m_connection->sendMessage(Message::newResponse(message.command(), message.requestId()));
×
211
          }
212
          else if(message.isRequest()) // send error response
×
213
          {
214
            m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1016_UNKNOWN_PROPERTY));
×
215
          }
216
        }
217
        else if(message.isRequest()) // send error response
×
218
        {
219
          m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT));
×
220
        }
×
221
      }
222
      return true;
×
223
    }
224
    case Message::Command::ObjectSetVectorProperty:
×
225
    {
226
      if(message.isRequest() || message.isEvent())
×
227
      {
228
        if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
229
        {
230
          if(AbstractVectorProperty* property = object->getVectorProperty(message.read<std::string>()); property && !property->isInternal())
×
231
          {
232
            try
233
            {
234
              const size_t index = message.read<uint32_t>();
×
235

236
              switch(message.read<ValueType>())
×
237
              {
238
                case ValueType::Boolean:
×
239
                  property->setBool(index, message.read<bool>());
×
240
                  break;
×
241

242
                case ValueType::Integer:
×
243
                  property->setInt64(index, message.read<int64_t>());
×
244
                  break;
×
245

246
                case ValueType::Float:
×
247
                  property->setDouble(index, message.read<double>());
×
248
                  break;
×
249

250
                case ValueType::String:
×
251
                  property->setString(index, message.read<std::string>());
×
252
                  break;
×
253

254
                default:
×
255
                  throw std::runtime_error("invalid value type");
×
256
              }
257
            }
258
            catch(const std::exception& e) // set property failed
×
259
            {
260
              if(message.isRequest()) // send error response
×
261
              {
262
                m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1018_EXCEPTION_X, e.what()));
×
263
              }
264
              else // send changed event with current value:
265
                objectPropertyChanged(*property);
×
266
            }
×
267

268
            if(message.isRequest()) // send success response
×
269
              m_connection->sendMessage(Message::newResponse(message.command(), message.requestId()));
×
270
          }
271
          else if(message.isRequest()) // send error response
×
272
          {
273
            m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1016_UNKNOWN_PROPERTY));
×
274
          }
275
        }
276
        else if(message.isRequest()) // send error response
×
277
        {
278
          m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT));
×
279
        }
×
280
      }
281
      return true;
×
282
    }
283
    case Message::Command::ObjectSetUnitPropertyUnit:
×
284
    {
285
      if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
286
      {
287
        if(AbstractUnitProperty* property = dynamic_cast<AbstractUnitProperty*>(object->getProperty(message.read<std::string>())); property && !property->isInternal())
×
288
        {
289
          try
290
          {
291
            property->setUnitValue(message.read<int64_t>());
×
292
          }
293
          catch(const std::exception&)
×
294
          {
295
            // set unit property unit failed, send changed event with current value:
296
            objectPropertyChanged(*property);
×
297
          }
×
298
        }
299
      }
×
300
      break;
×
301
    }
302
    case Message::Command::ObjectGetObjectPropertyObject:
×
303
      if(message.isRequest())
×
304
      {
305
        if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
306
        {
307
          if(auto* property = object->getObjectProperty(message.read<std::string>()); property && !property->isInternal())
×
308
          {
309
            if(auto obj = property->toObject())
×
310
            {
311
              auto response = Message::newResponse(message.command(), message.requestId());
×
312
              writeObject(*response, obj);
×
313
              m_connection->sendMessage(std::move(response));
×
314
            }
×
315
            else
316
              m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT));
×
317
          }
318
          else // send error response
319
            m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1016_UNKNOWN_PROPERTY));
×
320
        }
321
        else // send error response
322
          m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT));
×
323

324
        return true;
×
325
      }
326
      break;
×
327

328
    case Message::Command::ObjectGetObjectVectorPropertyObject:
×
329
      if(message.isRequest())
×
330
      {
331
        if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
332
        {
333
          if(auto* property = object->getVectorProperty(message.read<std::string>()); property && !property->isInternal())
×
334
          {
335
            const size_t startIndex = message.read<uint32_t>();
×
336
            const size_t endIndex = message.read<uint32_t>();
×
337

338
            if(endIndex >= startIndex && endIndex < property->size())
×
339
            {
340
              auto response = Message::newResponse(message.command(), message.requestId());
×
341
              for(size_t i = startIndex; i <= endIndex; i++)
×
342
                writeObject(*response, property->getObject(i));
×
343
              m_connection->sendMessage(std::move(response));
×
344
            }
×
345
            else // send error response
346
              m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1017_INVALID_INDICES));
×
347
          }
348
          else // send error response
349
            m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1016_UNKNOWN_PROPERTY));
×
350
        }
351
        else // send error response
352
          m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT));
×
353

354
        return true;
×
355
      }
356
      break;
×
357

358
    case Message::Command::ObjectSetObjectPropertyById:
×
359
    {
360
      if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
361
      {
362
        if(AbstractObjectProperty* property = dynamic_cast<AbstractObjectProperty*>(object->getProperty(message.read<std::string>())); property && !property->isInternal())
×
363
        {
364
          try
365
          {
366
            const auto id = message.read<std::string>();
×
367
            if(!id.empty())
×
368
            {
369
              if(ObjectPtr obj = Traintastic::instance->world->getObjectByPath(id))
×
370
                property->fromObject(obj);
×
371
              else
372
                throw std::runtime_error("");
×
373
            }
374
            else
375
              property->fromObject(nullptr);
×
376
          }
×
377
          catch(const std::exception&)
×
378
          {
379
            // set object property failed, send changed event with current value:
380
            objectPropertyChanged(*property);
×
381
          }
×
382
        }
383
      }
×
384
      break;
×
385
    }
386
    case Message::Command::ObjectCallMethod:
×
387
    {
388
      if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
389
      {
390
        if(AbstractMethod* method = object->getMethod(message.read<std::string>()); method && !method->isInternal())
×
391
        {
NEW
392
          if(callMethod(message, *method))
×
393
          {
NEW
394
            return true;
×
395
          }
396
        }
NEW
397
      }
×
NEW
398
      break;
×
399
    }
NEW
400
    case Message::Command::CallMethod:
×
401
    {
NEW
402
      const auto path = message.read<std::string_view>();
×
NEW
403
      const auto [objectPath, methodName] = splitOnLastDot(path);
×
404

NEW
405
      if(const auto& world = Traintastic::instance->world.value())
×
406
      {
NEW
407
        if(ObjectPtr object = world->getObjectByPath(objectPath))
×
408
        {
NEW
409
          if(AbstractMethod* method = object->getMethod(methodName); method && !method->isInternal())
×
410
          {
NEW
411
            if(callMethod(message, *method))
×
412
            {
413
              return true;
×
414
            }
415
          }
416
        }
×
417
      }
418
      break;
×
419
    }
420
    case Message::Command::GetTableModel:
×
421
    {
422
      if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
423
      {
424
        if(auto* table = dynamic_cast<Table*>(object.get()))
×
425
        {
426
          TableModelPtr model = table->getModel();
×
427
          assert(model);
×
428
          auto response = Message::newResponse(message.command(), message.requestId());
×
429
          writeTableModel(*response, model);
×
430
          m_connection->sendMessage(std::move(response));
×
431

432
          model->columnHeadersChanged = [this](const TableModelPtr& tableModel)
×
433
            {
434
              auto event = Message::newEvent(Message::Command::TableModelColumnHeadersChanged);
×
435
              event->write(m_handles.getHandle(std::dynamic_pointer_cast<Object>(tableModel)));
×
436
              event->write(tableModel->columnCount());
×
437
              for(const auto& text : tableModel->columnHeaders())
×
438
                event->write(text);
×
439
              m_connection->sendMessage(std::move(event));
×
440
            };
×
441

442
          model->rowCountChanged = [this](const TableModelPtr& tableModel)
×
443
            {
444
              auto event = Message::newEvent(Message::Command::TableModelRowCountChanged);
×
445
              event->write(m_handles.getHandle(std::dynamic_pointer_cast<Object>(tableModel)));
×
446
              event->write(tableModel->rowCount());
×
447
              m_connection->sendMessage(std::move(event));
×
448
            };
×
449

450
          model->updateRegion = [this](const TableModelPtr& tableModel, const TableModel::Region& region)
×
451
            {
452
              auto event = Message::newEvent(Message::Command::TableModelUpdateRegion);
×
453
              event->write(m_handles.getHandle(std::dynamic_pointer_cast<Object>(tableModel)));
×
454
              event->write(region.columnMin);
×
455
              event->write(region.columnMax);
×
456
              event->write(region.rowMin);
×
457
              event->write(region.rowMax);
×
458

459
              for(uint32_t row = region.rowMin; row <= region.rowMax; row++)
×
460
                for(uint32_t column = region.columnMin; column <= region.columnMax; column++)
×
461
                  event->write(tableModel->getText(column, row));
×
462

463
              m_connection->sendMessage(std::move(event));
×
464
            };
×
465

466
          return true;
×
467
        }
×
468
      }
×
469
      m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1019_OBJECT_NOT_A_TABLE));
×
470
      return true;
×
471
    }
472
    case Message::Command::ReleaseTableModel:
×
473
      m_handles.removeHandle(message.read<Handle>());
×
474
      break;
×
475

476
    case Message::Command::TableModelSetRegion:
×
477
    {
478
      TableModelPtr model = std::dynamic_pointer_cast<TableModel>(m_handles.getItem(message.read<Handle>()));
×
479
      if(model)
×
480
      {
481
        TableModel::Region region;
×
482
        message.read(region.columnMin);
×
483
        message.read(region.columnMax);
×
484
        message.read(region.rowMin);
×
485
        message.read(region.rowMax);
×
486
        model->setRegion(region);
×
487
      }
488
      break;
×
489
    }
×
490
    case Message::Command::InputMonitorGetInputInfo:
×
491
    {
492
      auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(m_handles.getItem(message.read<Handle>()));
×
493
      if(inputMonitor)
×
494
      {
495
        auto inputInfo = inputMonitor->getInputInfo();
×
496
        auto response = Message::newResponse(message.command(), message.requestId());
×
497
        response->write<uint32_t>(inputInfo.size());
×
498
        for(auto& info : inputInfo)
×
499
        {
500
          response->write(info.address);
×
501
          response->write(info.used);
×
502
          response->write(info.value);
×
503
        }
504
        m_connection->sendMessage(std::move(response));
×
505
        return true;
×
506
      }
×
507
      break;
×
508
    }
×
509
    case Message::Command::OutputKeyboardGetOutputInfo:
×
510
    {
511
      auto outputKeyboard = std::dynamic_pointer_cast<OutputKeyboard>(m_handles.getItem(message.read<Handle>()));
×
512
      if(outputKeyboard)
×
513
      {
514
        const auto outputType = outputKeyboard->outputType.value();
×
515
        auto outputInfo = outputKeyboard->getOutputInfo();
×
516
        auto response = Message::newResponse(message.command(), message.requestId());
×
517
        response->write<uint32_t>(outputInfo.size());
×
518
        for(auto& info : outputInfo)
×
519
        {
520
          response->write(info.address);
×
521
          response->write(info.used);
×
522
          switch(outputType)
×
523
          {
524
            case OutputType::Single:
×
525
              response->write(std::get<TriState>(info.value));
×
526
              break;
×
527

528
            case OutputType::Pair:
×
529
              response->write(std::get<OutputPairValue>(info.value));
×
530
              break;
×
531

532
            case OutputType::Aspect: /*[[unlikely]]*/
×
533
            case OutputType::ECoSState: /*[[unlikely]]*/
534
              assert(false);
×
535
              break;
536
          }
537
        }
538
        m_connection->sendMessage(std::move(response));
×
539
        return true;
×
540
      }
×
541
      break;
×
542
    }
×
543
    case Message::Command::BoardGetTileData:
×
544
    {
545
      auto board = std::dynamic_pointer_cast<Board>(m_handles.getItem(message.read<Handle>()));
×
546
      if(board)
×
547
      {
548
        auto response = Message::newResponse(message.command(), message.requestId());
×
549
        for(const auto& it : board->tileMap())
×
550
        {
551
          const Tile& tile = *(it.second);
×
552
          if(it.first != tile.location()) // only tiles at origin
×
553
            continue;
×
554
          response->write(tile.location());
×
555
          response->write(tile.data());
×
556
          assert(tile.data().isActive() == isActive(tile.data().id()));
×
557
          if(tile.data().isActive())
×
558
            writeObject(*response, it.second);
×
559
        }
560
        m_connection->sendMessage(std::move(response));
×
561
        return true;
×
562
      }
×
563
      break;
×
564
    }
×
565
    case Message::Command::BoardGetTileInfo:
×
566
    {
567
      auto response = Message::newResponse(message.command(), message.requestId());
×
568
      const auto& info = Tiles::getInfo();
×
569
      response->write<uint32_t>(info.size());
×
570
      for(const auto& item : info)
×
571
      {
572
        response->writeBlock();
×
573
        response->write(item.classId);
×
574
        response->write(item.tileId);
×
575
        response->write(item.rotates);
×
576
        response->write(item.menu);
×
577
        response->writeBlockEnd();
×
578
      }
579
      m_connection->sendMessage(std::move(response));
×
580
      return true;
×
581
    }
×
582
    case Message::Command::ServerLog:
×
583
      if(message.read<bool>())
×
584
      {
585
        if(auto* logger = Log::getMemoryLogger())
×
586
        {
587
          m_memoryLoggerChanged = logger->changed.connect(std::bind(&Session::memoryLoggerChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
×
588
          memoryLoggerChanged(*logger, logger->size(), 0);
×
589
        }
590
      }
591
      else // disable
592
        m_memoryLoggerChanged.disconnect();
×
593
      break;
×
594

595
    case Message::Command::ImportWorld:
×
596
      if(message.isRequest())
×
597
      {
598
        try
599
        {
600
          std::vector<std::byte> worldData;
×
601
          message.read(worldData);
×
602
          Traintastic::instance->importWorld(worldData);
×
603
          m_connection->sendMessage(Message::newResponse(message.command(), message.requestId()));
×
604
        }
×
605
        catch(const LogMessageException& e)
×
606
        {
607
          m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), e.message(), e.args()));
×
608
        }
×
609
      }
610
      break;
×
611

612
    case Message::Command::ExportWorld:
×
613
      if(message.isRequest())
×
614
      {
615
        if(Traintastic::instance->world)
×
616
        {
617
          try
618
          {
619
            std::vector<std::byte> worldData;
×
620
            Traintastic::instance->world->export_(worldData);
×
621
            auto response = Message::newResponse(message.command(), message.requestId());
×
622
            response->write(worldData);
×
623
            m_connection->sendMessage(std::move(response));
×
624
          }
×
625
          catch(const LogMessageException& e)
×
626
          {
627
            m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), e.message(), e.args()));
×
628
          }
×
629
        }
630
        else
631
        {
632
          m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1010_EXPORTING_WORLD_FAILED_X, "nullptr"));
×
633
        }
634
        return true;
×
635
      }
636
      break;
×
637

638
    case Message::Command::CreateObject:
×
639
      if(message.isRequest())
×
640
      {
641
        if(Traintastic::instance->world)
×
642
        {
643
          const auto classId = message.read<std::string_view>();
×
644
          if(classId == ClientThrottle::classId)
×
645
          {
646
            auto throttle = ClientThrottle::create(*Traintastic::instance->world);
×
647
            auto response = message.response();
×
648
            writeObject(*response, throttle);
×
649
            m_connection->sendMessage(std::move(response));
×
650
          }
×
651
          else
652
          {
653
            m_connection->sendMessage(message.errorResponse(LogMessage::C1015_UNKNOWN_OBJECT)); // FIXME change error
×
654
          }
655
        }
656
        else
657
        {
658
          m_connection->sendMessage(message.errorResponse(LogMessage::C1015_UNKNOWN_OBJECT)); // FIXME change error
×
659
        }
660
        return true;
×
661
      }
662
      break;
×
663

664
    case Message::Command::ObjectListGetObjects:
×
665
      if(message.isRequest())
×
666
      {
667
        if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
×
668
        {
669
          if(auto* list = dynamic_cast<AbstractObjectList*>(object.get()))
×
670
          {
671
            const uint32_t startIndex = message.read<uint32_t>();
×
672
            const uint32_t endIndex = message.read<uint32_t>();
×
673

674
            if(endIndex >= startIndex && endIndex < list->length.value())
×
675
            {
676
              auto response = message.response();
×
677
              for(uint32_t i = startIndex; i <= endIndex; i++)
×
678
              {
679
                writeObject(*response, list->getObject(i));
×
680
              }
681
              m_connection->sendMessage(std::move(response));
×
682
            }
×
683
            else // send error response
684
            {
685
              m_connection->sendMessage(message.errorResponse(LogMessage::C1017_INVALID_INDICES));
×
686
            }
687
          }
688
          else
689
          {
690
            m_connection->sendMessage(message.errorResponse(LogMessage::C1015_UNKNOWN_OBJECT));
×
691
          }
692
        }
693
        else
694
        {
695
          m_connection->sendMessage(message.errorResponse(LogMessage::C1015_UNKNOWN_OBJECT));
×
696
        }
×
697
        return true;
×
698
      }
699
      break;
×
700

701
    default:
×
702
      break;
×
703
  }
704
  return false;
×
705
}
706

707
bool Session::isSessionObject(const ObjectPtr& object)
×
708
{
709
  assert(object);
×
710
  return dynamic_cast<ClientThrottle*>(object.get());
×
711
}
712

NEW
713
bool Session::callMethod(const Message& message, AbstractMethod& method)
×
714
{
NEW
715
  const auto resultType = message.read<ValueType>();
×
NEW
716
  const auto argumentCount = message.read<uint8_t>();
×
717

NEW
718
  Arguments args;
×
NEW
719
  for(uint8_t i = 0; i < argumentCount; i++)
×
720
  {
NEW
721
    switch(message.read<ValueType>())
×
722
    {
NEW
723
      case ValueType::Boolean:
×
NEW
724
        args.push_back(message.read<bool>());
×
NEW
725
        break;
×
726

NEW
727
      case ValueType::Enum:
×
728
      case ValueType::Integer:
729
      case ValueType::Set:
NEW
730
        args.push_back(message.read<int64_t>());
×
NEW
731
        break;
×
732

NEW
733
      case ValueType::Float:
×
NEW
734
        args.push_back(message.read<double>());
×
NEW
735
        break;
×
736

NEW
737
      case ValueType::String:
×
738
      {
NEW
739
        auto arg = message.read<std::string>();
×
NEW
740
        if(i < method.argumentTypeInfo().size() && method.argumentTypeInfo()[i].type == ValueType::Object)
×
741
        {
NEW
742
          if(arg.empty())
×
NEW
743
            args.push_back(ObjectPtr());
×
NEW
744
          else if(ObjectPtr obj = Traintastic::instance->world->getObjectByPath(arg))
×
NEW
745
            args.push_back(obj);
×
746
          else
NEW
747
            args.push_back(arg);
×
748
        }
749
        else
NEW
750
          args.push_back(arg);
×
NEW
751
        break;
×
NEW
752
      }
×
NEW
753
      case ValueType::Object:
×
NEW
754
        args.push_back(m_handles.getItem(message.read<Handle>()));
×
NEW
755
        break;
×
756

NEW
757
      default:
×
NEW
758
        assert(false);
×
759
        break;
760
    }
761
  }
762

763
  try
764
  {
NEW
765
    AbstractMethod::Result result = method.call(args);
×
766

NEW
767
    if(message.isRequest())
×
768
    {
NEW
769
      auto response = Message::newResponse(message.command(), message.requestId());
×
770

NEW
771
      switch(resultType)
×
772
      {
NEW
773
        case ValueType::Boolean:
×
NEW
774
          response->write(std::get<bool>(result));
×
NEW
775
          break;
×
776

NEW
777
        case ValueType::Integer:
×
778
        case ValueType::Enum:
779
        case ValueType::Set:
NEW
780
          response->write(std::get<int64_t>(result));
×
NEW
781
          break;
×
782

NEW
783
        case ValueType::Float:
×
NEW
784
          response->write(std::get<double>(result));
×
NEW
785
          break;
×
786

NEW
787
        case ValueType::String:
×
NEW
788
          response->write(std::get<std::string>(result));
×
NEW
789
          break;
×
790

NEW
791
        case ValueType::Object:
×
NEW
792
          writeObject(*response, std::get<ObjectPtr>(result));
×
NEW
793
          break;
×
794

NEW
795
        case ValueType::Invalid:
×
NEW
796
          break;
×
797
      }
798

NEW
799
      m_connection->sendMessage(std::move(response));
×
NEW
800
      return true;
×
NEW
801
    }
×
NEW
802
  }
×
NEW
803
  catch(const LogMessageException& e)
×
804
  {
NEW
805
    if(message.isRequest())
×
806
    {
NEW
807
      m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), e.message(), e.args()));
×
NEW
808
      return true;
×
809
    }
810
    // we can't report it back to the caller, so just log it.
NEW
811
    Log::log(method.object(), e.message(), e.args());
×
NEW
812
  }
×
NEW
813
  catch(const std::exception& e)
×
814
  {
NEW
815
    if(message.isRequest())
×
816
    {
NEW
817
      m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1018_EXCEPTION_X, e.what()));
×
NEW
818
      return true;
×
819
    }
NEW
820
  }
×
NEW
821
  return false;
×
NEW
822
}
×
823

UNCOV
824
void Session::writeObject(Message& message, const ObjectPtr& object)
×
825
{
826
  message.writeBlock(); // object
×
827

828
  auto handle = m_handles.getHandle(object);
×
829
  if(handle == Handles::invalidHandle)
×
830
  {
831
    using namespace std::placeholders;
832

833
    handle = m_handles.addItem(object);
×
834

835
    m_objectSignals.emplace(handle, object->onDestroying.connect(std::bind(&Session::objectDestroying, this, std::placeholders::_1)));
×
836
    m_objectSignals.emplace(handle, object->propertyChanged.connect(std::bind(&Session::objectPropertyChanged, this, std::placeholders::_1)));
×
837
    m_objectSignals.emplace(handle, object->attributeChanged.connect(std::bind(&Session::objectAttributeChanged, this, std::placeholders::_1)));
×
838

839
    if(auto* board = dynamic_cast<Board*>(object.get()))
×
840
    {
841
      m_objectSignals.emplace(handle, board->tileDataChanged.connect(std::bind(&Session::boardTileDataChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
×
842
    }
843

844
    bool hasPublicEvents = false;
×
845

846
    message.write(handle);
×
847
    message.write(object->getClassId());
×
848

849
    message.writeBlock(); // items
×
850
    const InterfaceItems& interfaceItems = object->interfaceItems();
×
851
    for(const auto& name : interfaceItems.names())
×
852
    {
853
      InterfaceItem& item = interfaceItems[name];
×
854

855
      if(item.isInternal())
×
856
        continue;
×
857

858
      message.writeBlock(); // item
×
859
      message.write(name);
×
860

861
      if(auto* baseProperty = dynamic_cast<BaseProperty*>(&item))
×
862
      {
863
        AbstractProperty* property = nullptr;
×
864
        AbstractUnitProperty* unitProperty = nullptr;
×
865
        AbstractVectorProperty* vectorProperty = nullptr;
×
866

867
        if((property = dynamic_cast<AbstractProperty*>(baseProperty)))
×
868
        {
869
          if((unitProperty = dynamic_cast<AbstractUnitProperty*>(property)))
×
870
            message.write(InterfaceItemType::UnitProperty);
×
871
          else
872
            message.write(InterfaceItemType::Property);
×
873
        }
874
        else if((vectorProperty = dynamic_cast<AbstractVectorProperty*>(baseProperty)))
×
875
          message.write(InterfaceItemType::VectorProperty);
×
876
        else
877
        {
878
          assert(false);
×
879
          continue;
880
        }
881

882
        message.write(baseProperty->flags());
×
883
        message.write(baseProperty->type());
×
884

885
        if(baseProperty->type() == ValueType::Enum)
×
886
          message.write(baseProperty->enumName());
×
887
        else if(baseProperty->type() == ValueType::Set)
×
888
          message.write(baseProperty->setName());
×
889

890
        if(property)
×
891
        {
892
          writePropertyValue(message, *property);
×
893

894
          if(unitProperty)
×
895
          {
896
            message.write(unitProperty->unitName());
×
897
            message.write(unitProperty->unitValue());
×
898
          }
899
        }
900
        else if(vectorProperty)
×
901
          writeVectorPropertyValue(message, *vectorProperty);
×
902
        else
903
          assert(false);
×
904
      }
905
      else if(const auto* method = dynamic_cast<const AbstractMethod*>(&item))
×
906
      {
907
        message.write(InterfaceItemType::Method);
×
908
        message.write(method->resultTypeInfo().type);
×
909
        message.write(static_cast<uint8_t>(method->argumentTypeInfo().size()));
×
910
        for(const auto& info : method->argumentTypeInfo())
×
911
          message.write(info.type);
×
912
      }
913
      else if(const auto* event = dynamic_cast<const AbstractEvent*>(&item))
×
914
      {
915
        hasPublicEvents = true;
×
916

917
        message.write(InterfaceItemType::Event);
×
918
        message.write(static_cast<uint8_t>(event->argumentTypeInfo().size()));
×
919
        for(const auto& typeInfo : event->argumentTypeInfo())
×
920
          writeTypeInfo(message, typeInfo);
×
921
      }
922
      else
923
        assert(false);
×
924

925
      message.writeBlock(); // attributes
×
926

927
      for(const auto& it : item.attributes())
×
928
      {
929
        const AbstractAttribute& attribute = *it.second;
×
930
        message.writeBlock(); // attribute
×
931
        writeAttribute(message, attribute);
×
932
        message.writeBlockEnd(); // end attribute
×
933
      }
934

935
      message.writeBlockEnd(); // end attributes
×
936
      message.writeBlockEnd(); // end item
×
937
    }
938
    message.writeBlockEnd(); // end items
×
939

940
    if(hasPublicEvents)
×
941
      m_objectSignals.emplace(handle, object->onEventFired.connect(std::bind(&Session::objectEventFired, this, std::placeholders::_1, std::placeholders::_2)));
×
942
  }
943
  else
944
    message.write(handle);
×
945

946
  message.writeBlockEnd(); // end object
×
947
}
×
948

949
void Session::writeTableModel(Message& message, const TableModelPtr& model)
×
950
{
951
  message.writeBlock(); // model
×
952
  assert(m_handles.getHandle(std::dynamic_pointer_cast<Object>(model)) == Handles::invalidHandle);
×
953
  auto handle = m_handles.addItem(std::dynamic_pointer_cast<Object>(model));
×
954
  message.write(handle);
×
955
  message.write(model->getClassId());
×
956
  message.write(model->columnCount());
×
957
  for(const auto& text : model->columnHeaders())
×
958
    message.write(text);
×
959
  message.write(model->rowCount());
×
960
  message.writeBlockEnd(); // end model
×
961
}
×
962

963
void Session::memoryLoggerChanged(const MemoryLogger& logger, const uint32_t added, const uint32_t removed)
×
964
{
965
  auto event = Message::newEvent(Message::Command::ServerLog);
×
966
  event->write(removed);
×
967
  event->write(added);
×
968

969
  const uint32_t size = logger.size();
×
970
  for(uint32_t i = size - added; i < size; i++)
×
971
  {
972
    const auto& log = logger[i];
×
973
    event->write((std::chrono::duration_cast<std::chrono::microseconds>(log.time.time_since_epoch())).count());
×
974
    event->write(log.objectId);
×
975
    event->write(log.message);
×
976
    const size_t argc = log.args ? std::min<size_t>(log.args->size(), std::numeric_limits<uint8_t>::max()) : 0;
×
977
    event->write(static_cast<uint8_t>(argc));
×
978
    for(size_t j = 0; j < argc; j++)
×
979
      event->write(log.args->at(j));
×
980
  }
981

982
  m_connection->sendMessage(std::move(event));
×
983
}
×
984

985
void Session::objectDestroying(Object& object)
×
986
{
987
  const auto handle = m_handles.getHandle(object.shared_from_this());
×
988
  m_handles.removeHandle(handle);
×
989
  m_objectSignals.erase(handle);
×
990

991
  auto event = Message::newEvent(Message::Command::ObjectDestroyed, sizeof(Handle));
×
992
  event->write(handle);
×
993
  m_connection->sendMessage(std::move(event));
×
994
}
×
995

996
void Session::objectPropertyChanged(BaseProperty& baseProperty)
×
997
{
998
  if(baseProperty.isInternal())
×
999
    return;
×
1000

1001
  auto event = Message::newEvent(Message::Command::ObjectPropertyChanged);
×
1002
  event->write(m_handles.getHandle(baseProperty.object().shared_from_this()));
×
1003
  event->write(baseProperty.name());
×
1004
  event->write(baseProperty.type());
×
1005
  if(auto* property = dynamic_cast<AbstractProperty*>(&baseProperty))
×
1006
  {
1007
    writePropertyValue(*event, *property);
×
1008

1009
    if(auto* unitProperty = dynamic_cast<AbstractUnitProperty*>(property))
×
1010
      event->write(unitProperty->unitValue());
×
1011
  }
1012
  else if(auto* vectorProperty = dynamic_cast<AbstractVectorProperty*>(&baseProperty))
×
1013
    writeVectorPropertyValue(*event, *vectorProperty);
×
1014
  else
1015
    assert(false);
×
1016

1017
  m_connection->sendMessage(std::move(event));
×
1018
}
×
1019

1020
void Session::writePropertyValue(Message& message , const AbstractProperty& property)
×
1021
{
1022
  switch(property.type())
×
1023
  {
1024
    case ValueType::Boolean:
×
1025
      message.write(property.toBool());
×
1026
      break;
×
1027

1028
    case ValueType::Enum:
×
1029
    case ValueType::Integer:
1030
    case ValueType::Set:
1031
      message.write(property.toInt64());
×
1032
      break;
×
1033

1034
    case ValueType::Float:
×
1035
      message.write(property.toDouble());
×
1036
      break;
×
1037

1038
    case ValueType::String:
×
1039
      message.write(property.toString());
×
1040
      break;
×
1041

1042
    case ValueType::Object:
×
1043
      if(ObjectPtr obj = property.toObject())
×
1044
        message.write(obj->getObjectId());
×
1045
      else
1046
        message.write<std::string_view>("");
×
1047
      break;
×
1048

1049
    case ValueType::Invalid:
×
1050
      assert(false);
×
1051
      break;
1052
  }
1053
}
×
1054

1055
void Session::writeVectorPropertyValue(Message& message , const AbstractVectorProperty& vectorProperty)
×
1056
{
1057
  const size_t size = vectorProperty.size();
×
1058
  message.write(static_cast<uint32_t>(size));
×
1059

1060
  switch(vectorProperty.type())
×
1061
  {
1062
    case ValueType::Boolean:
×
1063
      for(size_t i = 0; i < size; i++)
×
1064
        message.write(vectorProperty.getBool(i));
×
1065
      break;
×
1066

1067
    case ValueType::Enum:
×
1068
    case ValueType::Integer:
1069
    case ValueType::Set:
1070
      for(size_t i = 0; i < size; i++)
×
1071
        message.write(vectorProperty.getInt64(i));
×
1072
      break;
×
1073

1074
    case ValueType::Float:
×
1075
      for(size_t i = 0; i < size; i++)
×
1076
        message.write(vectorProperty.getDouble(i));
×
1077
      break;
×
1078

1079
    case ValueType::String:
×
1080
      for(size_t i = 0; i < size; i++)
×
1081
        message.write(vectorProperty.getString(i));
×
1082
      break;
×
1083

1084
    case ValueType::Object:
×
1085
      for(size_t i = 0; i < size; i++)
×
1086
        if(ObjectPtr obj = vectorProperty.getObject(i))
×
1087
          message.write(obj->getObjectId());
×
1088
        else
1089
          message.write<std::string>("");
×
1090
      break;
×
1091

1092
    case ValueType::Invalid:
×
1093
      assert(false);
×
1094
      break;
1095
  }
1096
}
×
1097

1098
void Session::objectAttributeChanged(AbstractAttribute& attribute)
×
1099
{
1100
  auto event = Message::newEvent(Message::Command::ObjectAttributeChanged);
×
1101
  event->write(m_handles.getHandle(attribute.item().object().shared_from_this()));
×
1102
  event->write(attribute.item().name());
×
1103
  writeAttribute(*event, attribute);
×
1104
  m_connection->sendMessage(std::move(event));
×
1105
}
×
1106

1107
void Session::objectEventFired(const AbstractEvent& event, const Arguments& arguments)
×
1108
{
1109
  auto message = Message::newEvent(Message::Command::ObjectEventFired);
×
1110
  message->write(m_handles.getHandle(event.object().shared_from_this()));
×
1111
  message->write(event.name());
×
1112
  message->write(static_cast<uint32_t>(arguments.size()));
×
1113
  size_t i = 0;
×
1114
  for(const auto& typeInfo : event.argumentTypeInfo())
×
1115
  {
1116
    switch(typeInfo.type)
×
1117
    {
1118
      case ValueType::Boolean:
×
1119
        message->write(std::get<bool>(arguments[i]));
×
1120
        break;
×
1121

1122
      case ValueType::Enum:
×
1123
      case ValueType::Integer:
1124
      case ValueType::Set:
1125
        message->write(std::get<int64_t>(arguments[i]));
×
1126
        break;
×
1127

1128
      case ValueType::Float:
×
1129
        message->write(std::get<double>(arguments[i]));
×
1130
        break;
×
1131

1132
      case ValueType::String:
×
1133
        message->write(std::get<std::string>(arguments[i]));
×
1134
        break;
×
1135

1136
      case ValueType::Object:
×
1137
        if(ObjectPtr obj = std::get<ObjectPtr>(arguments[i]))
×
1138
          message->write(obj->getObjectId());
×
1139
        else
1140
          message->write(std::string_view{});
×
1141
        break;
×
1142

1143
      case ValueType::Invalid:
×
1144
        assert(false);
×
1145
        return;
1146
    }
1147
    i++;
×
1148
  }
1149
  m_connection->sendMessage(std::move(message));
×
1150
}
×
1151

1152
void Session::writeAttribute(Message& message , const AbstractAttribute& attribute)
×
1153
{
1154
  message.write(attribute.name());
×
1155
  message.write(attribute.type());
×
1156
  if(const auto* valueAttribute = dynamic_cast<const AbstractValueAttribute*>(&attribute))
×
1157
  {
1158
    message.write(AttributeType::Value);
×
1159
    switch(attribute.type())
×
1160
    {
1161
      case ValueType::Boolean:
×
1162
        message.write(valueAttribute->toBool());
×
1163
        break;
×
1164

1165
      case ValueType::Enum:
×
1166
      case ValueType::Integer:
1167
        message.write(valueAttribute->toInt64());
×
1168
        break;
×
1169

1170
      case ValueType::Float:
×
1171
        message.write(valueAttribute->toDouble());
×
1172
        break;
×
1173

1174
      case ValueType::String:
×
1175
        message.write(valueAttribute->toString());
×
1176
        break;
×
1177

1178
      default:
×
1179
        assert(false);
×
1180
        break;
1181
    }
1182
  }
1183
  else if(const auto* valuesAttributes = dynamic_cast<const AbstractValuesAttribute*>(&attribute))
×
1184
  {
1185
    const uint32_t length = valuesAttributes->length();
×
1186
    message.write(AttributeType::Values);
×
1187
    message.write(length);
×
1188
    switch(attribute.type())
×
1189
    {
1190
      case ValueType::Boolean:
×
1191
        for(uint32_t i = 0; i < length; i++)
×
1192
          message.write(valuesAttributes->getBool(i));
×
1193
        break;
×
1194

1195
      case ValueType::Enum:
×
1196
      case ValueType::Integer:
1197
        for(uint32_t i = 0; i < length; i++)
×
1198
          message.write(valuesAttributes->getInt64(i));
×
1199
        break;
×
1200

1201
      case ValueType::Float:
×
1202
        for(uint32_t i = 0; i < length; i++)
×
1203
          message.write(valuesAttributes->getDouble(i));
×
1204
        break;
×
1205

1206
      case ValueType::String:
×
1207
        for(uint32_t i = 0; i < length; i++)
×
1208
          message.write(valuesAttributes->getString(i));
×
1209
        break;
×
1210

1211
      default:
×
1212
        assert(false);
×
1213
        break;
1214
    }
1215
  }
1216
  else
1217
    assert(false);
×
1218
}
×
1219

1220
void Session::writeTypeInfo(Message& message, const TypeInfo& typeInfo)
×
1221
{
1222
  assert((typeInfo.type == ValueType::Enum) != typeInfo.enumName.empty());
×
1223
  assert((typeInfo.type == ValueType::Set) != typeInfo.setName.empty());
×
1224

1225
  message.write(typeInfo.type);
×
1226
  if(typeInfo.type == ValueType::Enum)
×
1227
    message.write(typeInfo.enumName);
×
1228
  else if(typeInfo.type == ValueType::Set)
×
1229
    message.write(typeInfo.setName);
×
1230
}
×
1231

1232
void Session::boardTileDataChanged(Board& board, const TileLocation& location, const TileData& data)
×
1233
{
1234
  auto event = Message::newEvent(Message::Command::BoardTileDataChanged);
×
1235
  event->write(m_handles.getHandle(board.shared_from_this()));
×
1236
  event->write(location);
×
1237
  event->write(data);
×
1238
  assert(data.isActive() == isActive(data.id()));
×
1239
  if(data.isActive())
×
1240
  {
1241
    auto tile = board.getTile(location);
×
1242
    assert(tile);
×
1243
    writeObject(*event, tile);
×
1244
  }
×
1245
  m_connection->sendMessage(std::move(event));
×
1246
}
×
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