Implemented the real server

This commit is contained in:
Vladislav Khorev committed 2026-10-30 16:37:30 +06:00
1 parent 931236bec0
commit 075750442f
22 files changed
+1130 -44

No files matched your search

+189
View File
@@ -0,0 +1,189 @@
#include "WebsocketGameClient.h"
#include "NetworkProtocol.h"
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <atomic>
#include <iostream>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
namespace FRG {
namespace {
namespace asio = boost::asio;
namespace beast = boost::beast;
namespace websocket = beast::websocket;
using tcp = asio::ip::tcp;
} // namespace
class WebsocketGameClient::Impl {
public:
Impl(std::string host, std::string port, std::string path)
: resolver_(io_)
, socket_(io_)
, host_(std::move(host))
, port_(std::move(port))
, path_(std::move(path))
{
resolve();
thread_ = std::thread([this]() { io_.run(); });
}
~Impl()
{
io_.stop();
if (thread_.joinable()) thread_.join();
}
std::vector<std::string> takeMessages()
{
std::lock_guard<std::mutex> lock(messagesMutex_);
std::vector<std::string> result;
result.swap(messages_);
return result;
}
bool connected() const { return connected_.load(); }
private:
void resolve()
{
resolver_.async_resolve(
host_, port_,
[this](beast::error_code error, tcp::resolver::results_type results) {
if (error) return fail("resolve", error);
asio::async_connect(
socket_.next_layer(), results,
[this](beast::error_code connectError,
const tcp::endpoint&) {
if (connectError) return fail("connect", connectError);
socket_.set_option(
websocket::stream_base::timeout::suggested(
beast::role_type::client));
socket_.async_handshake(
host_, path_,
[this](beast::error_code handshakeError) {
if (handshakeError)
return fail("handshake", handshakeError);
connected_.store(true);
std::cout
<< "[network] Connected to ws://"
<< host_ << ':' << port_ << path_ << '\n';
read();
});
});
});
}
void read()
{
socket_.async_read(
buffer_,
[this](beast::error_code error, std::size_t) {
if (error) {
connected_.store(false);
if (error != websocket::error::closed) fail("read", error);
return;
}
std::string message = beast::buffers_to_string(buffer_.data());
buffer_.consume(buffer_.size());
{
std::lock_guard<std::mutex> lock(messagesMutex_);
messages_.push_back(std::move(message));
}
read();
});
}
void fail(const char* operation, beast::error_code error)
{
connected_.store(false);
std::cerr << "[network] WebSocket " << operation << " failed: "
<< error.message() << '\n';
}
asio::io_context io_;
tcp::resolver resolver_;
websocket::stream<tcp::socket> socket_;
beast::flat_buffer buffer_;
std::thread thread_;
std::string host_;
std::string port_;
std::string path_;
std::atomic_bool connected_{false};
std::mutex messagesMutex_;
std::vector<std::string> messages_;
};
WebsocketGameClient::WebsocketGameClient(
std::string host, std::string port, std::string path)
: impl_(std::make_unique<Impl>(
std::move(host), std::move(port), std::move(path)))
{
}
WebsocketGameClient::~WebsocketGameClient() = default;
std::shared_ptr<const GameSnapshot> WebsocketGameClient::snapshot() const
{
return replica_.snapshot();
}
const std::string& WebsocketGameClient::localPlayerId() const
{
return replica_.localPlayerId();
}
void WebsocketGameClient::submit(const GameCommand&)
{
// Commands intentionally remain local no-ops in the first iteration.
}
std::vector<GameServerEvent> WebsocketGameClient::drainEvents()
{
return {};
}
void WebsocketGameClient::update(std::int64_t)
{
std::vector<std::string> messages = impl_->takeMessages();
if (messages.empty()) return;
// State messages are complete snapshots; only the newest queued one matters.
for (auto it = messages.rbegin(); it != messages.rend(); ++it) {
nlohmann::json state;
std::string error;
if (!parseStateMessage(*it, state, error)) {
std::cerr << "[network] Ignoring WebSocket message: "
<< error << '\n';
continue;
}
try {
replica_.load(state);
} catch (const std::exception& exception) {
std::cerr << "[network] Ignoring invalid game state: "
<< exception.what() << '\n';
}
break;
}
}
void WebsocketGameClient::save(nlohmann::json&) const
{
// Persistence remains server-side.
}
void WebsocketGameClient::load(const nlohmann::json&)
{
// Persistence remains server-side.
}
bool WebsocketGameClient::connected() const
{
return impl_->connected();
}
} // namespace FRG