Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions server/src/throttle/throttle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
#include "../core/objectproperty.hpp"
#include "../core/objectvectorproperty.hpp"
#include "../core/method.hpp"
#include "../utils/stringequal.hpp"
#include "../utils/stringhash.hpp"
#include <traintastic/utils/stringequal.hpp>
#include <traintastic/utils/stringhash.hpp>

class Decoder;
enum class DecoderProtocol : uint8_t;
Expand Down
12 changes: 12 additions & 0 deletions shared/src/traintastic/simulator/protocol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ enum class OpCode : uint8_t
LocomotiveSpeedDirection = 2,
SensorChanged = 3,
AccessorySetState = 4,
Handshake = 5,
HandshakeResponse = 6
};

struct Message
Expand All @@ -50,6 +52,16 @@ struct Message
};
static_assert(sizeof(Message) == 2);

struct HandShake : Message
{
HandShake(bool reply)
: Message(reply ? OpCode::HandshakeResponse : OpCode::Handshake,
sizeof(HandShake))
{
}
};
static_assert(sizeof(HandShake) == 2);

struct Power : Message
{
uint8_t powerOn;
Expand Down
132 changes: 127 additions & 5 deletions shared/src/traintastic/simulator/simulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "simulatorconnection.hpp"
#include <numbers>
#include <ranges>
#include <bit>
#include "protocol.hpp"

namespace
Expand Down Expand Up @@ -315,8 +316,11 @@ std::optional<T> stringToEnum(std::string_view value)
Simulator::Simulator(const nlohmann::json& world)
: staticData(load(world, m_stateData))
, m_tickTimer{m_ioContext}
, m_handShakeTimer{m_ioContext}
, m_acceptor{m_ioContext}
, m_socketUDP{m_ioContext}
{

}

Simulator::~Simulator()
Expand All @@ -342,10 +346,10 @@ uint16_t Simulator::serverPort() const
return m_acceptor.local_endpoint().port();
}

void Simulator::start()
void Simulator::start(bool discoverable)
{
m_thread = std::thread(
[this]()
[this, discoverable]()
{
if(m_serverEnabled)
{
Expand All @@ -358,19 +362,36 @@ void Simulator::start()

m_acceptor.listen(5, ec);

if(discoverable)
{
m_socketUDP.open(boost::asio::ip::udp::v4(), ec);
if(ec)
assert(false);

m_socketUDP.set_option(boost::asio::socket_base::reuse_address(true), ec);
if(ec)
assert(false);

m_socketUDP.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), defaultPort), ec);
if(ec)
assert(false);

doReceive();
}

accept();
}
tick();
handShake();
m_ioContext.run();
});
}

void Simulator::stop()
{
// Stop UDP discovery
boost::system::error_code ec;
m_acceptor.cancel(ec);
m_acceptor.close(ec);
// ignore errors
m_socketUDP.close(ec);

while(!m_connections.empty())
{
Expand All @@ -379,7 +400,12 @@ void Simulator::stop()
connection->stop();
}

// Stop TCP server
m_acceptor.cancel(ec);
m_acceptor.close(ec);

m_tickTimer.cancel();
m_handShakeTimer.cancel();
if(m_thread.joinable())
{
m_thread.join();
Expand Down Expand Up @@ -640,6 +666,9 @@ void Simulator::receive(const SimulatorProtocol::Message& message)
}
case OpCode::SensorChanged:
break; // only sent by simulator
case OpCode::Handshake:
case OpCode::HandshakeResponse:
break; // handled by SimulatorConnection already
}
}

Expand All @@ -648,9 +677,15 @@ void Simulator::removeConnection(const std::shared_ptr<SimulatorConnection>& con
if(auto it = std::find(m_connections.begin(), m_connections.end(), connection); it != m_connections.end())
{
m_connections.erase(it);
onConnectionRemoved(connection);
}
}

void Simulator::onConnectionRemoved(const std::shared_ptr<SimulatorConnection>&)
{

}

void Simulator::accept()
{
m_acceptor.async_accept(
Expand All @@ -659,11 +694,56 @@ void Simulator::accept()
if(!ec)
{
m_connections.emplace_back(std::make_shared<SimulatorConnection>(shared_from_this(), std::move(socket)))->start();
sendInitialState(*m_connections.rbegin());
accept();
}
});
}

constexpr char RequestMessage[] = {'s', 'i', 'm', '?'};
constexpr char ResponseMessage[] = {'s', 'i', 'm', '!'};

void Simulator::doReceive()
{
m_socketUDP.async_receive_from(
boost::asio::buffer(m_udpBuffer),
m_remoteEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytesReceived)
{
if(!ec)
{
const char *recvMsg = reinterpret_cast<char*>(m_udpBuffer.data());
if(bytesReceived >= sizeof(RequestMessage) && std::memcmp(recvMsg, &RequestMessage, sizeof(RequestMessage)) == 0)
{
if(!m_serverLocalHostOnly || m_remoteEndpoint.address().is_loopback())
{
uint16_t response[3] = {0, 0, serverPort()};

// Send in big endian format
if constexpr (std::endian::native == std::endian::little)
{
// Swap bytes
uint8_t b[2] = {};
*reinterpret_cast<uint16_t *>(b) = response[2];
std::swap(b[0], b[1]);
response[2] = *reinterpret_cast<uint16_t *>(b);
}

std::memcpy(&response, &ResponseMessage, sizeof(ResponseMessage));
m_socketUDP.async_send_to(boost::asio::buffer(response, sizeof(response)), m_remoteEndpoint,
[this](const boost::system::error_code& ec2, std::size_t bytesTransferred)
{
assert(!ec2 && bytesTransferred == 6);
doReceive();
});
return;
}
}
doReceive();
}
});
}

void Simulator::tick()
{
m_tickTimer.expires_after(tickRate);
Expand All @@ -689,6 +769,36 @@ void Simulator::tick()
onTick();
}

void Simulator::handShake()
{
m_handShakeTimer.expires_after(handShakeRate);
m_handShakeTimer.async_wait(
[this](std::error_code ec)
{
if(!ec)
{
handShake();
}
});

auto it = m_connections.begin();
while(it != m_connections.end())
{
if(!(*it)->handShakeResponseReceived())
{
std::shared_ptr<SimulatorConnection> conn = *it;
it = m_connections.erase(it);
conn->stop();
onConnectionRemoved(conn);
continue;
}

(*it)->setHandShakeResponseReceived(false);
(*it)->send(SimulatorProtocol::HandShake(false));
it++;
}
}

void Simulator::updateTrainPositions()
{
if(staticData.trackSegments.empty()) [[unlikely]]
Expand Down Expand Up @@ -1532,3 +1642,15 @@ Simulator::StaticData Simulator::load(const nlohmann::json& world, StateData& st

return data;
}

void Simulator::sendInitialState(const std::shared_ptr<SimulatorConnection> &connection)
{
// Send current sensor state
const size_t count = staticData.sensors.size();
for(size_t i = 0; i < count; ++i)
{
const auto& sensor = staticData.sensors[i];
auto& sensorState = m_stateData.sensors[i];
connection->send(SimulatorProtocol::SensorChanged(sensor.channel, sensor.address, sensorState.value));
}
}
17 changes: 16 additions & 1 deletion shared/src/traintastic/simulator/simulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <vector>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/signals2/signal.hpp>
#include <nlohmann/json.hpp>
Expand Down Expand Up @@ -247,7 +248,7 @@ class Simulator : public std::enable_shared_from_this<Simulator>

uint16_t serverPort() const;

void start();
void start(bool discoverable = false);
void stop();

void setPowerOn(bool powerOn);
Expand All @@ -267,20 +268,34 @@ class Simulator : public std::enable_shared_from_this<Simulator>

private:
constexpr static auto tickRate = std::chrono::milliseconds(1000 / 30);
constexpr static auto handShakeRate = std::chrono::milliseconds(1000);

boost::asio::io_context m_ioContext;
boost::asio::steady_timer m_tickTimer;
boost::asio::steady_timer m_handShakeTimer;
boost::asio::ip::tcp::acceptor m_acceptor;
boost::asio::ip::udp::socket m_socketUDP;
std::array<char, 8> m_udpBuffer;
boost::asio::ip::udp::endpoint m_remoteEndpoint;

std::thread m_thread;
mutable std::mutex m_stateMutex;
bool m_serverEnabled = false;
bool m_serverLocalHostOnly = true;
static constexpr uint16_t defaultPort = 5741; // UDP Discovery
uint16_t m_serverPort = 5741;

size_t lastConnectionId = 0;
std::list<std::shared_ptr<SimulatorConnection>> m_connections;

void accept();
void doReceive();
void onConnectionRemoved(const std::shared_ptr<SimulatorConnection> &);

void sendInitialState(const std::shared_ptr<SimulatorConnection>& connection);

void tick();
void handShake();

void updateTrainPositions();
bool updateVehiclePosition(VehicleState::Face& face, const float speed);
Expand Down
15 changes: 13 additions & 2 deletions shared/src/traintastic/simulator/simulatorconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ SimulatorConnection::SimulatorConnection(std::shared_ptr<Simulator> simulator, b
: m_simulator{std::move(simulator)}
, m_socket{std::move(socket)}
{
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));
m_socket.set_option(boost::asio::socket_base::send_buffer_size(8192 * 2));
}

void SimulatorConnection::start()
Expand Down Expand Up @@ -61,7 +63,6 @@ bool SimulatorConnection::send(const SimulatorProtocol::Message& message)
return true;
}


void SimulatorConnection::read()
{
m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
Expand All @@ -81,7 +82,17 @@ void SimulatorConnection::read()
break;
}

m_simulator->receive(*message);
if(message->opCode == SimulatorProtocol::OpCode::HandshakeResponse &&
message->size == sizeof(SimulatorProtocol::HandShake))
{
// Eat message
m_handShakeResponseReceived = true;
}
else
{
m_simulator->receive(*message);
}

pos += message->size;
bytesTransferred -= message->size;
}
Expand Down
8 changes: 8 additions & 0 deletions shared/src/traintastic/simulator/simulatorconnection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class SimulatorConnection : public std::enable_shared_from_this<SimulatorConnect
size_t m_readBufferOffset = 0;
std::array<std::byte, 1024> m_writeBuffer;
size_t m_writeBufferOffset = 0;
bool m_handShakeResponseReceived = true;

void read();
void write();
Expand All @@ -53,6 +54,13 @@ class SimulatorConnection : public std::enable_shared_from_this<SimulatorConnect
void stop();

bool send(const SimulatorProtocol::Message& message);

bool handShakeResponseReceived() const { return m_handShakeResponseReceived; }

void setHandShakeResponseReceived(bool newHandShakeResponseReceived)
{
m_handShakeResponseReceived = newHandShakeResponseReceived;
}
};

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#ifndef TRAINTASTIC_SERVER_UTILS_STRINGEQUAL_HPP
#define TRAINTASTIC_SERVER_UTILS_STRINGEQUAL_HPP
#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_UTILS_STRINGEQUAL_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_UTILS_STRINGEQUAL_HPP

#include <string>
#include <string_view>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#ifndef TRAINTASTIC_SERVER_UTILS_STRINGHASH_HPP
#define TRAINTASTIC_SERVER_UTILS_STRINGHASH_HPP
#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_UTILS_STRINGHASH_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_UTILS_STRINGHASH_HPP

#include <string>
#include <string_view>
Expand Down
1 change: 1 addition & 0 deletions simulator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ target_link_libraries(traintastic-simulator PRIVATE Qt6::Core Qt6::Widgets Qt6::
if(WIN32)
target_compile_definitions(traintastic-simulator PRIVATE WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x1000)
target_link_libraries(traintastic-simulator PRIVATE OpenGL32)
target_link_libraries(traintastic-simulator PRIVATE wsock32)
target_sources(traintastic-simulator PRIVATE ../shared/gfx/appicon.rc src/version.rc)
set_property(TARGET traintastic-simulator PROPERTY WIN32_EXECUTABLE true)
endif()
Expand Down
Loading
Loading