From e0fe6693e007e6df8df63230695393572c231635 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Fri, 19 Jan 2024 17:33:53 +0100 Subject: [PATCH] implement vehicle specifics, code needed for the rest of the server --- include/Network.h | 105 ++++++++++++++++++++++++--- src/Network.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 262 insertions(+), 23 deletions(-) diff --git a/include/Network.h b/include/Network.h index 849f706..bbb03da 100644 --- a/include/Network.h +++ b/include/Network.h @@ -1,14 +1,22 @@ #pragma once +#include "Common.h" +#include "Packet.h" #include "State.h" #include "Sync.h" #include "Transport.h" -#include "Packet.h" #include #include +#include #include #include +#include +#include +#include #include +#include +#include +#include #include #include @@ -17,11 +25,10 @@ using VehicleID = uint16_t; using namespace boost::asio; - struct Client { using Ptr = std::shared_ptr; ClientID id; - bmp::State state { bmp::State::None }; + Sync state { bmp::State::None }; Sync name; Sync role; @@ -64,8 +71,51 @@ private: struct Vehicle { using Ptr = std::shared_ptr; - ClientID owner; - std::vector data; + Sync owner; + Sync> data; + + struct Status { + glm::vec3 rvel {}; + glm::vec4 rot {}; + glm::vec3 vel {}; + glm::vec3 pos {}; + float time {}; + }; + + Status get_status() { + std::unique_lock lock(m_mtx); + refresh_cache(lock); + return { + .rvel = m_rvel, + .rot = m_rot, + .vel = m_vel, + .pos = m_pos, + .time = m_time, + }; + } + + void update_status(const std::vector& raw_packet) { + std::unique_lock lock(m_mtx); + m_needs_refresh = true; + m_status_data = raw_packet; + } + +private: + std::recursive_mutex m_mtx; + + /// Holds pos, rvel, vel, etc. raw, updated every time + /// such a packet arrives. + std::vector m_status_data; + + /// Parses the status_data on request sets needs_refresh = false. + void refresh_cache(std::unique_lock& lock); + + bool m_needs_refresh = true; + glm::vec3 m_rvel {}; + glm::vec4 m_rot {}; + glm::vec3 m_vel {}; + glm::vec3 m_pos {}; + float m_time {}; }; class Network { @@ -73,16 +123,51 @@ public: Network(); ~Network(); + friend Client; + + void disconnect(ClientID id, const std::string& msg); + + void send_to(ClientID id, const bmp::Packet& packet); + + /// Returns a map of containing only clients which are + /// fully connected, i.e. who have mods downloaded and everything spawned in. + /// If you're unsure which to use, use this one. + std::unordered_map playing_clients() const; + /// Returns a map of containing only clients who are authenticated. + std::unordered_map authenticated_clients() const; + /// Returns all clients, including non-authenticated clients. Use only for debugging, + /// information, stats, status. + std::unordered_map all_clients() const; + + std::optional get_client(ClientID id, bmp::State min_state) const; + + std::unordered_map get_vehicles_owned_by(ClientID id); + + std::optional get_vehicle(VehicleID id) { + auto vehicles = m_vehicles.synchronize(); + if (vehicles->contains(id)) { + return vehicles->at(id); + } else { + return std::nullopt; + } + } + + size_t authenticated_client_count() const; + + size_t clients_in_state_count(bmp::State state) const; + + size_t guest_count() const; + + size_t vehicle_count() const; + +private: + void handle_packet(ClientID id, const bmp::Packet& packet); + /// Reads a packet from the given UDP socket, returning the client's endpoint as an out-argument. bmp::Packet udp_read(ip::udp::endpoint& out_ep); /// Sends a packet to the specified UDP endpoint via the UDP socket. void udp_write(bmp::Packet& packet, const ip::udp::endpoint& to_ep); - void disconnect(ClientID id, const std::string& msg); - - void handle_packet(ClientID i, const bmp::Packet& packet); - -private: void udp_read_main(); void tcp_listen_main(); diff --git a/src/Network.cpp b/src/Network.cpp index 6523b68..0715b9e 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -40,17 +40,16 @@ bmp::Packet Client::tcp_read() { return packet; } +void Network::send_to(ClientID id, const bmp::Packet& packet) { +} + void Client::tcp_write(bmp::Packet& packet) { - beammp_tracef("Sending 0x{:x} to {}", int(packet.purpose), id); // acquire a lock to avoid writing a header, then being interrupted by another write std::unique_lock lock(m_tcp_write_mtx); // finalize the packet (compress etc) and produce header auto header = packet.finalize(); // serialize header std::vector header_data(bmp::Header::SERIALIZED_SIZE); - if (header.flags != bmp::Flags::None) { - beammp_errorf("Flags are not implemented"); - } header.serialize_to(header_data); // write header and packet data write(m_tcp_socket, buffer(header_data)); @@ -120,10 +119,6 @@ bmp::Packet Network::udp_read(ip::udp::endpoint& out_ep) { bmp::Packet packet; bmp::Header header {}; auto offset = header.deserialize_from(s_buffer); - if (header.flags != bmp::Flags::None) { - beammp_errorf("Flags are not implemented"); - return {}; - } packet.raw_data.resize(header.size); std::copy(s_buffer.begin() + offset, s_buffer.begin() + offset + header.size, packet.raw_data.begin()); return packet; @@ -146,13 +141,25 @@ Network::Network() m_tcp_listen_thread.interrupt(); m_udp_read_thread.interrupt(); }); + Application::SetSubsystemStatus("Network", Application::Status::Good); } Network::~Network() { + Application::SetSubsystemStatus("Network", Application::Status::ShuttingDown); *m_shutdown = true; + if (!m_tcp_listen_thread.try_join_for(boost::chrono::seconds(1))) { + m_tcp_listen_thread.detach(); + Application::SetSubsystemStatus("TCP", Application::Status::Shutdown); + } + if (!m_udp_read_thread.try_join_for(boost::chrono::seconds(1))) { + m_udp_read_thread.detach(); + Application::SetSubsystemStatus("UDP", Application::Status::Shutdown); + } + Application::SetSubsystemStatus("Network", Application::Status::Shutdown); } void Network::tcp_listen_main() { + Application::SetSubsystemStatus("TCP", Application::Status::Starting); ip::tcp::endpoint listen_ep(ip::address::from_string("0.0.0.0"), static_cast(Application::Settings.Port)); ip::tcp::socket listener(m_threadpool); boost::system::error_code ec; @@ -178,6 +185,7 @@ void Network::tcp_listen_main() { ec.message()); Application::GracefullyShutdown(); } + Application::SetSubsystemStatus("TCP", Application::Status::Good); while (!*m_shutdown) { auto new_socket = acceptor.accept(); if (ec) { @@ -190,10 +198,13 @@ void Network::tcp_listen_main() { std::shared_ptr new_client(std::make_shared(new_id, *this, std::move(new_socket))); m_clients->emplace(new_id, new_client); } + Application::SetSubsystemStatus("TCP", Application::Status::Shutdown); } void Network::udp_read_main() { + Application::SetSubsystemStatus("UDP", Application::Status::Starting); m_udp_socket = ip::udp::socket(m_io, ip::udp::endpoint(ip::udp::v4(), Application::Settings.Port)); + Application::SetSubsystemStatus("UDP", Application::Status::Good); while (!*m_shutdown) { try { ip::udp::endpoint ep; @@ -245,6 +256,7 @@ void Network::udp_read_main() { beammp_errorf("Failed to UDP read: {}", e.what()); } } + Application::SetSubsystemStatus("UDP", Application::Status::Shutdown); } void Network::disconnect(ClientID id, const std::string& msg) { @@ -265,8 +277,42 @@ void Network::disconnect(ClientID id, const std::string& msg) { const auto& [key, value] = item; return value == id; }); + // TODO: Despawn vehicles owned by this player clients->erase(id); } + +std::unordered_map Network::playing_clients() const { + std::unordered_map copy {}; + auto clients = m_clients.synchronize(); + copy.reserve(clients->size()); + for (const auto& [id, client] : *clients) { + if (client->state == bmp::State::Playing) { + copy[id] = client; + } + } + return copy; +} +std::unordered_map Network::authenticated_clients() const { + std::unordered_map copy {}; + auto clients = m_clients.synchronize(); + copy.reserve(clients->size()); + for (const auto& [id, client] : *clients) { + if (client->state >= bmp::State::Authentication) { + copy[id] = client; + } + } + return copy; +} +std::unordered_map Network::all_clients() const { + return *m_clients; +} +size_t Network::authenticated_client_count() const { + auto clients = m_clients.synchronize(); + return size_t(std::count_if(clients->begin(), clients->end(), [](const auto& pair) { + return pair.second->state >= bmp::State::Authentication; + })); +} + void Network::handle_packet(ClientID id, const bmp::Packet& packet) { std::shared_ptr client; { @@ -277,7 +323,7 @@ void Network::handle_packet(ClientID id, const bmp::Packet& packet) { } client = clients->at(id); } - switch (client->state) { + switch (*client->state) { case bmp::State::None: // move to identification client->state = bmp::State::Identification; @@ -346,14 +392,15 @@ void Network::handle_identification(ClientID id, const bmp::Packet& packet, std: .patch = version.patch, }, .implementation = { - .value = "Official BeamMP Server (BeamMP Ltd.)", + .value = "Official BeamMP Server", }, }; bmp::Packet sinfo_packet { .purpose = bmp::ServerInfo, .raw_data = std::vector(1024), }; - sinfo.serialize_to(sinfo_packet.raw_data); + auto offset = sinfo.serialize_to(sinfo_packet.raw_data); + sinfo_packet.raw_data.resize(offset); client->tcp_write(sinfo_packet); // now transfer to next state bmp::Packet auth_state { @@ -364,7 +411,7 @@ void Network::handle_identification(ClientID id, const bmp::Packet& packet, std: break; } default: - beammp_errorf("Got 0x{:x} in state {}. This is not allowed. Disconnecting the client", uint16_t(packet.purpose), int(client->state)); + beammp_errorf("Got 0x{:x} in state {}. This is not allowed. Disconnecting the client", uint16_t(packet.purpose), int(client->state.get())); disconnect(id, "invalid purpose in current state"); } } @@ -485,7 +532,114 @@ void Network::handle_authentication(ClientID id, const bmp::Packet& packet, std: break; } default: - beammp_errorf("Got 0x{:x} in state {}. This is not allowed. Disconnecting the client", uint16_t(packet.purpose), int(client->state)); + beammp_errorf("Got 0x{:x} in state {}. This is not allowed. Disconnecting the client", uint16_t(packet.purpose), int(client->state.get())); disconnect(id, "invalid purpose in current state"); } } +std::optional Network::get_client(ClientID id, bmp::State min_state) const { + auto clients = m_clients.synchronize(); + if (clients->contains(id)) { + auto client = clients->at(id); + if (client->state >= min_state) { + return clients->at(id); + } else { + beammp_warnf("Tried to get client {}, but client is not yet in state {} (is in state {})", id, int(min_state), int(client->state.get())); + return std::nullopt; + } + } else { + return std::nullopt; + } +} +std::unordered_map Network::get_vehicles_owned_by(ClientID id) { + auto vehicles = m_vehicles.synchronize(); + std::unordered_map result {}; + for (const auto& [vid, vehicle] : *vehicles) { + if (vehicle->owner == id) { + result[vid] = vehicle; + } + } + return result; +} + +void Vehicle::refresh_cache(std::unique_lock& lock) { + (void)lock; + if (!m_needs_refresh) { + return; + } + try { + auto json = nlohmann::json::parse(m_status_data.data()); + if (json["rvel"].is_array()) { + auto array = json["rvel"].get>(); + m_rvel = { + array.at(0), + array.at(1), + array.at(2) + }; + } + + if (json["rot"].is_array()) { + auto array = json["rot"].get>(); + m_rot = { + array.at(0), + array.at(1), + array.at(2), + array.at(3), + }; + } + if (json["vel"].is_array()) { + auto array = json["vel"].get>(); + m_vel = { + array.at(0), + array.at(1), + array.at(2) + }; + } + if (json["pos"].is_array()) { + auto array = json["pos"].get>(); + m_pos = { + array.at(0), + array.at(1), + array.at(2) + }; + } + if (json["tim"].is_number()) { + m_time = json["tim"].get(); + } + } catch (const std::exception& e) { + beammp_errorf("Invalid position data: {}", e.what()); + } + m_needs_refresh = false; +} + +TEST_CASE("Vehicle position parse, cache, access") { + Vehicle veh {}; + std::string str = R"({"rvel":[0.034001241344458,0.016966195008928,-0.0032029844877877],"rot":[-0.0012675799979579,0.0014056711767528,0.94126306518056,0.3376688606555],"tim":66.978502945043,"vel":[-18.80228647297,22.830758602197,0.0011466381380035],"pos":[562.68027268429,-379.27891669179,160.40605946989],"ping":0.032000000871718})"; + veh.update_status(std::vector(str.begin(), str.end())); + auto status = veh.get_status(); + constexpr double EPS = 0.00001; + CHECK_LT(std::abs(status.rvel.x - 0.034001241344458f), EPS); + CHECK_LT(std::abs(status.rvel.y - 0.016966195008928f), EPS); + CHECK_LT(std::abs(status.rvel.z - -0.0032029844877877f), EPS); + CHECK_LT(std::abs(status.rot.x - -0.0012675799979579f), EPS); + CHECK_LT(std::abs(status.rot.y - 0.0014056711767528f), EPS); + CHECK_LT(std::abs(status.rot.z - 0.94126306518056f), EPS); + CHECK_LT(std::abs(status.rot.w - 0.3376688606555f), EPS); + CHECK_LT(std::abs(status.time - 66.978502945043f), EPS); + CHECK_LT(std::abs(status.vel.x - -18.80228647297f), EPS); + CHECK_LT(std::abs(status.vel.y - 22.830758602197f), EPS); + CHECK_LT(std::abs(status.vel.z - 0.0011466381380035f), EPS); + CHECK_LT(std::abs(status.pos.x - 562.68027268429f), EPS); + CHECK_LT(std::abs(status.pos.y - -379.27891669179f), EPS); + CHECK_LT(std::abs(status.pos.z - 160.40605946989f), EPS); +} +size_t Network::guest_count() const { + auto clients = m_clients.synchronize(); + return size_t(std::count_if(clients->begin(), clients->end(), [](const auto& pair) { return pair.second->is_guest; })); +} + +size_t Network::clients_in_state_count(bmp::State state) const { + auto clients = m_clients.synchronize(); + return size_t(std::count_if(clients->begin(), clients->end(), [&state](const auto& pair) { return pair.second->state == state; })); +} + +size_t Network::vehicle_count() const { return m_vehicles->size(); }