Skip to content

Commit 452466a

Browse files
committed
Add high-level multiplayer packet management
Add PacketType tests
1 parent 82e38a8 commit 452466a

13 files changed

Lines changed: 1254 additions & 0 deletions

src/openvic-simulation/GameManager.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#include "GameManager.hpp"
22

33
#include <chrono>
4+
#include <string_view>
5+
6+
#include "openvic-simulation/multiplayer/ClientManager.hpp"
7+
#include "openvic-simulation/multiplayer/HostManager.hpp"
48

59
using namespace OpenVic;
610

@@ -125,6 +129,17 @@ bool GameManager::update_clock() {
125129
return instance_manager->update_clock();
126130
}
127131

132+
void GameManager::create_client() {
133+
client_manager = memory::make_unique<ClientManager>(this);
134+
}
135+
136+
void GameManager::create_host(std::string_view session_name) {
137+
host_manager = memory::make_unique<HostManager>(this);
138+
if (!session_name.empty()) {
139+
host_manager->get_host_session().set_game_name(memory::string { session_name });
140+
}
141+
}
142+
128143
uint64_t GameManager::get_elapsed_microseconds() {
129144
return get_elapsed_usec_time_callback();
130145
}

src/openvic-simulation/GameManager.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@
22

33
#include <optional>
44
#include <span>
5+
#include <string_view>
56

67
#include "openvic-simulation/DefinitionManager.hpp"
78
#include "openvic-simulation/InstanceManager.hpp"
89
#include "openvic-simulation/dataloader/ModManager.hpp"
910
#include "openvic-simulation/dataloader/Dataloader.hpp"
1011
#include "openvic-simulation/misc/GameRulesManager.hpp"
1112
#include "openvic-simulation/gen/commit_info.gen.hpp"
13+
#include "openvic-simulation/multiplayer/ClientManager.hpp"
14+
#include "openvic-simulation/multiplayer/HostManager.hpp"
1215

1316
#include <function2/function2.hpp>
1417

1518
namespace OpenVic {
19+
struct HostManager;
20+
struct ClientManager;
21+
1622
struct GameManager {
1723
using elapsed_time_getter_func_t = fu2::function_base<true, true, fu2::capacity_none, false, false, uint64_t() const>;
1824

@@ -30,6 +36,9 @@ namespace OpenVic {
3036
bool PROPERTY_CUSTOM_PREFIX(definitions_loaded, are);
3137
bool PROPERTY_CUSTOM_PREFIX(mod_descriptors_loaded, are);
3238

39+
memory::unique_ptr<HostManager> host_manager;
40+
memory::unique_ptr<ClientManager> client_manager;
41+
3342
public:
3443
GameManager(
3544
InstanceManager::gamestate_updated_func_t new_gamestate_updated_callback,
@@ -45,6 +54,22 @@ namespace OpenVic {
4554
return instance_manager ? &*instance_manager : nullptr;
4655
}
4756

57+
inline HostManager* get_host_manager() {
58+
return host_manager.get();
59+
}
60+
61+
inline HostManager const* get_host_manager() const {
62+
return host_manager.get();
63+
}
64+
65+
inline ClientManager* get_client_manager() {
66+
return client_manager.get();
67+
}
68+
69+
inline ClientManager const* get_client_manager() const {
70+
return client_manager.get();
71+
}
72+
4873
bool set_roots(Dataloader::path_span_t roots, Dataloader::path_span_t replace_paths);
4974

5075
bool load_mod_descriptors(std::span<const memory::string> descriptors);
@@ -57,6 +82,9 @@ namespace OpenVic {
5782

5883
bool update_clock();
5984

85+
void create_client();
86+
void create_host(std::string_view session_name = "");
87+
6088
static constexpr std::string_view get_commit_hash() {
6189
return SIM_COMMIT_HASH;
6290
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
#include "BaseMultiplayerManager.hpp"
3+
4+
#include <any>
5+
6+
#include "openvic-simulation/multiplayer/PacketType.hpp"
7+
#include "openvic-simulation/utility/ErrorMacros.hpp"
8+
9+
using namespace OpenVic;
10+
11+
BaseMultiplayerManager::BaseMultiplayerManager(GameManager* game_manager) : game_manager(game_manager) {
12+
packet_cache.reserve_power(15);
13+
}
14+
15+
bool BaseMultiplayerManager::broadcast_packet(PacketType const& type, std::any const& argument) {
16+
OV_ERR_FAIL_COND_V(!PacketType::is_valid_type(type), false);
17+
return true;
18+
}
19+
20+
bool BaseMultiplayerManager::send_packet(client_id_type client_id, PacketType const& type, std::any const& argument) {
21+
OV_ERR_FAIL_COND_V(!PacketType::is_valid_type(type), false);
22+
return true;
23+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#pragma once
2+
3+
#include <any>
4+
#include <cstdint>
5+
6+
#include "openvic-simulation/multiplayer/HostSession.hpp"
7+
#include "openvic-simulation/multiplayer/PacketType.hpp"
8+
#include "openvic-simulation/multiplayer/lowlevel/ReliableUdpClient.hpp"
9+
#include "openvic-simulation/types/RingBuffer.hpp"
10+
#include "openvic-simulation/utility/Containers.hpp"
11+
#include "openvic-simulation/utility/Getters.hpp"
12+
13+
namespace OpenVic {
14+
struct GameManager;
15+
16+
struct BaseMultiplayerManager {
17+
BaseMultiplayerManager(GameManager* game_manager = nullptr);
18+
19+
using client_id_type = uint64_t;
20+
using sequence_type = ReliableUdpClient::sequence_type;
21+
22+
virtual bool broadcast_packet(PacketType const& type, std::any const& argument);
23+
virtual bool send_packet(client_id_type client_id, PacketType const& type, std::any const& argument);
24+
virtual int64_t poll() = 0;
25+
26+
enum class ConnectionType : uint8_t { HOST, CLIENT };
27+
28+
virtual ConnectionType get_connection_type() const = 0;
29+
30+
template<typename T>
31+
T* cast_as() {
32+
if (get_connection_type() == T::type_tag) {
33+
return static_cast<T*>(this);
34+
}
35+
return nullptr;
36+
}
37+
38+
PacketSpan get_last_raw_packet() {
39+
return last_raw_packet;
40+
}
41+
42+
protected:
43+
bool PROPERTY_ACCESS(in_lobby, protected, false);
44+
GameManager* PROPERTY_PTR_ACCESS(game_manager, protected);
45+
HostSession PROPERTY_ACCESS(host_session, protected);
46+
47+
RingBuffer<uint8_t> packet_cache;
48+
49+
struct PacketCacheIndex {
50+
decltype(packet_cache)::const_iterator begin;
51+
decltype(packet_cache)::const_iterator end;
52+
constexpr bool is_valid() const {
53+
return begin != end;
54+
}
55+
};
56+
57+
memory::vector<uint8_t> last_raw_packet;
58+
59+
friend bool PacketTypes::send_raw_packet_process_callback( //
60+
BaseMultiplayerManager* multiplayer_manager, PacketSpan packet
61+
);
62+
};
63+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
2+
#include "ClientManager.hpp"
3+
4+
#include <cstdio>
5+
#include <filesystem>
6+
#include <string_view>
7+
#include <system_error>
8+
9+
#include "openvic-simulation/GameManager.hpp"
10+
#include "openvic-simulation/multiplayer/PacketType.hpp"
11+
#include "openvic-simulation/multiplayer/lowlevel/HostnameAddress.hpp"
12+
#include "openvic-simulation/multiplayer/lowlevel/NetworkError.hpp"
13+
#include "openvic-simulation/multiplayer/lowlevel/NetworkSocket.hpp"
14+
#include "openvic-simulation/multiplayer/lowlevel/PacketBuilder.hpp"
15+
#include "openvic-simulation/multiplayer/lowlevel/ReliableUdpClient.hpp"
16+
#include "openvic-simulation/utility/Containers.hpp"
17+
#include "openvic-simulation/utility/ErrorMacros.hpp"
18+
19+
using namespace OpenVic;
20+
21+
bool ClientManager::connect_to(HostnameAddress const& address, NetworkSocket::port_type port) {
22+
NetworkError err = client.connect_to(address.resolved_address(), port);
23+
OV_ERR_FAIL_COND_V(err != NetworkError::OK, false);
24+
PacketBuilder builder;
25+
builder.put_back<uint8_t>(1);
26+
return client.set_packet(builder) == NetworkError::OK;
27+
}
28+
29+
template<PacketType const& type>
30+
bool ClientManager::_send_packet_to_host(auto const& argument) {
31+
PacketBuilder builder;
32+
builder.put_back(type.packet_id);
33+
if (!type.create_packet(this, argument, builder)) {
34+
return false;
35+
}
36+
37+
if (!add_packet_to_cache(builder)) {
38+
return false;
39+
}
40+
41+
client.set_packet(builder);
42+
return true;
43+
}
44+
45+
bool ClientManager::broadcast_packet(PacketType const& type, std::any const& argument) {
46+
if (!BaseMultiplayerManager::broadcast_packet(type, argument)) {
47+
return false;
48+
}
49+
50+
OV_ERR_FAIL_COND_V(!type.can_client_send, false);
51+
return _send_packet_to_host<PacketTypes::broadcast_packet>(BroadcastData { type.packet_id, argument });
52+
}
53+
54+
bool ClientManager::send_packet(client_id_type client_id, PacketType const& type, std::any const& argument) {
55+
if (!BaseMultiplayerManager::send_packet(client_id, type, argument)) {
56+
return false;
57+
}
58+
59+
OV_ERR_FAIL_COND_V(!type.can_client_send, false);
60+
return _send_packet_to_host<PacketTypes::retransmit_packet>(RetransmitData { client_id, type.packet_id, argument });
61+
}
62+
63+
int64_t ClientManager::poll() {
64+
int64_t result = client.available_packets();
65+
if (result < 1) {
66+
return result;
67+
}
68+
69+
PacketSpan span = client.packet_span();
70+
if (client_id == INVALID_CLIENT_ID) {
71+
OV_ERR_FAIL_COND_V(client.get_current_sequence_value() != 0, -1);
72+
client_id = span.read<decltype(client_id)>();
73+
OV_ERR_FAIL_COND_V(client_id == INVALID_CLIENT_ID, -1);
74+
return poll();
75+
}
76+
77+
decltype(PacketType::packet_id) packet_id = span.read<decltype(packet_id)>();
78+
OV_ERR_FAIL_COND_V(!PacketType::is_valid_type(packet_id), -1);
79+
80+
PacketType const& packet_type = PacketType::get_type_by_id(packet_id);
81+
if (!packet_type.process_packet(this, span)) {
82+
// TODO: packet processing failed
83+
return -1;
84+
}
85+
// TODO: packet was processed
86+
87+
return result;
88+
}
89+
90+
bool ClientManager::connect_to_resource_server(std::optional<NetworkSocket::port_type> port) {
91+
if (is_running_as_host()) {
92+
return true;
93+
}
94+
95+
NetworkError err = resource_client.connect_to(client.get_peer_ip(), port.value_or(client.get_peer_port() + 1));
96+
OV_ERR_FAIL_COND_V(err != NetworkError::OK, false);
97+
return true;
98+
}
99+
100+
void ClientManager::poll_resource_download() {
101+
if (is_running_as_host()) {
102+
return;
103+
}
104+
105+
if (resource_client.poll() != NetworkError::OK || resource_client.get_status() != TcpPacketStream::Status::CONNECTED) {
106+
return;
107+
}
108+
109+
PacketBuffer buffer = resource_client.packet_buffer(sizeof(size_t));
110+
size_t buffer_size = buffer.read<size_t>();
111+
buffer.clear();
112+
buffer.resize(buffer_size);
113+
resource_client.get_data(buffer);
114+
std::string_view str = buffer.read<std::string_view>();
115+
std::filesystem::path path = str;
116+
size_t index = buffer.index();
117+
std::span<const uint8_t> data { &buffer.data()[index], buffer.size() - index };
118+
119+
// TODO: separate file writing into a separate thread?
120+
std::error_code ec;
121+
122+
OV_ERR_FAIL_COND(!std::filesystem::create_directories(path.parent_path(), ec) || !ec);
123+
124+
{
125+
std::FILE* file = std::fopen(path.c_str(), "w");
126+
OV_ERR_FAIL_COND(file == nullptr);
127+
128+
size_t write_count = std::fwrite(data.data(), sizeof(uint8_t), data.size(), file);
129+
std::fclose(file);
130+
OV_ERR_FAIL_COND(write_count != buffer_size);
131+
}
132+
}
133+
134+
bool ClientManager::is_running_as_host() const {
135+
return game_manager->get_host_manager() != nullptr;
136+
}
137+
138+
bool ClientManager::add_packet_to_cache(std::span<uint8_t> bytes) {
139+
OV_ERR_FAIL_COND_V(bytes.size_bytes() > ReliableUdpClient::MAX_PACKET_SIZE, false);
140+
decltype(packet_cache)::iterator begin = packet_cache.append(bytes.data(), bytes.size_bytes());
141+
decltype(packet_cache)::iterator end = packet_cache.end();
142+
OV_ERR_FAIL_COND_V(begin == end, false);
143+
return sequence_to_index.try_emplace(client.get_next_sequence_value(), PacketCacheIndex { begin, end }).second;
144+
}
145+
146+
memory::vector<uint8_t> ClientManager::get_packet_cache(sequence_type sequence_value) {
147+
decltype(sequence_to_index)::iterator it = sequence_to_index.find(sequence_value);
148+
OV_ERR_FAIL_COND_V(it == sequence_to_index.end(), {});
149+
return { it.value().begin, it.value().end };
150+
}
151+
152+
void ClientManager::remove_from_cache(sequence_type sequence_value) {
153+
decltype(sequence_to_index)::iterator it = sequence_to_index.find(sequence_value);
154+
OV_ERR_FAIL_COND(it == sequence_to_index.end());
155+
sequence_to_index.unordered_erase(it);
156+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#pragma once
2+
3+
#include <limits>
4+
#include <optional>
5+
6+
#include "openvic-simulation/multiplayer/BaseMultiplayerManager.hpp"
7+
#include "openvic-simulation/multiplayer/lowlevel/HostnameAddress.hpp"
8+
#include "openvic-simulation/multiplayer/lowlevel/NetworkSocket.hpp"
9+
#include "openvic-simulation/multiplayer/lowlevel/ReliableUdpClient.hpp"
10+
#include "openvic-simulation/multiplayer/lowlevel/TcpPacketStream.hpp"
11+
#include "openvic-simulation/utility/Containers.hpp"
12+
13+
namespace OpenVic {
14+
struct GameManager;
15+
16+
struct ClientManager final : BaseMultiplayerManager {
17+
using BaseMultiplayerManager::BaseMultiplayerManager;
18+
19+
bool connect_to(HostnameAddress const& address, NetworkSocket::port_type port);
20+
21+
bool broadcast_packet(PacketType const& type, std::any const& argument) override;
22+
bool send_packet(client_id_type client_id, PacketType const& type, std::any const& argument) override;
23+
int64_t poll() override;
24+
25+
bool connect_to_resource_server(std::optional<NetworkSocket::port_type> port = std::nullopt);
26+
void poll_resource_download();
27+
28+
bool is_running_as_host() const;
29+
30+
static constexpr ConnectionType type_tag = ConnectionType::CLIENT;
31+
inline constexpr ConnectionType get_connection_type() const override {
32+
return type_tag;
33+
}
34+
35+
static constexpr client_id_type INVALID_CLIENT_ID = std::numeric_limits<client_id_type>::max();
36+
37+
private:
38+
ReliableUdpClient PROPERTY_REF(client);
39+
TcpPacketStream resource_client;
40+
client_id_type PROPERTY(client_id, INVALID_CLIENT_ID);
41+
42+
friend bool PacketTypes::update_host_session_process_callback(BaseMultiplayerManager* game_manager, PacketSpan packet);
43+
44+
ordered_map<sequence_type, PacketCacheIndex> sequence_to_index;
45+
46+
bool add_packet_to_cache(std::span<uint8_t> bytes);
47+
memory::vector<uint8_t> get_packet_cache(sequence_type sequence_value);
48+
void remove_from_cache(sequence_type sequence_value);
49+
50+
template<PacketType const& type>
51+
bool _send_packet_to_host(auto const& argument);
52+
};
53+
}

0 commit comments

Comments
 (0)