Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Player name #53

Merged
merged 3 commits into from
Nov 14, 2024
Merged
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
3 changes: 2 additions & 1 deletion src/network/include/network/message_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
#include <cstdint>

enum class MessageType : std::uint8_t {
Heartbeat = 0,
Connect,
Heartbeat,
GridState,
GameStart,
StateBroadcast,
Expand Down
57 changes: 49 additions & 8 deletions src/network/include/network/messages.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ struct AbstractMessage {
[[nodiscard]] virtual bool equals(AbstractMessage const& other) const = 0;
};

static constexpr auto player_name_buffer_size = usize{ 32 };

struct Connect final : AbstractMessage {
std::string player_name;

explicit Connect(std::string_view player_name);

[[nodiscard]] static constexpr decltype(MessageHeader::payload_size) max_payload_size() {
return static_cast<decltype(MessageHeader::payload_size)>(player_name_buffer_size);
}

[[nodiscard]] MessageType type() const override;
[[nodiscard]] decltype(MessageHeader::payload_size) payload_size() const override;
[[nodiscard]] c2k::MessageBuffer serialize() const override;
[[nodiscard]] static Connect deserialize(c2k::MessageBuffer& buffer);

[[nodiscard]] bool equals(AbstractMessage const& other) const override;
};

struct Heartbeat final : AbstractMessage {
public:
std::uint64_t frame;
Expand Down Expand Up @@ -107,44 +126,66 @@ struct GridState final : AbstractMessage {
}
};

struct ClientIdentity final {
u8 client_id;
std::string player_name;

ClientIdentity(u8 const client_id, std::string player_name)
: client_id{ client_id }, player_name{ std::move(player_name) } {}

[[nodiscard]] bool operator==(ClientIdentity const& other) const = default;
};

struct GameStart final : AbstractMessage {
std::uint8_t client_id;
std::uint64_t start_frame;
std::uint64_t random_seed;
std::uint8_t num_players;
std::vector<ClientIdentity> client_identities;

GameStart(
std::uint8_t const client_id,
std::uint64_t const start_frame,
std::uint64_t const random_seed,
std::uint8_t const num_players
std::vector<ClientIdentity> client_identities
)
: client_id{ client_id }, start_frame{ start_frame }, random_seed{ random_seed }, num_players{ num_players } {}
: client_id{ client_id },
start_frame{ start_frame },
random_seed{ random_seed },
client_identities{ std::move(client_identities) } {
if (this->client_identities.size() > std::numeric_limits<u8>::max()) {
throw std::invalid_argument{ "Number of clients is too high." };
}
}

[[nodiscard]] MessageType type() const override;
[[nodiscard]] decltype(MessageHeader::payload_size) payload_size() const override;
[[nodiscard]] c2k::MessageBuffer serialize() const override;
[[nodiscard]] static GameStart deserialize(c2k::MessageBuffer& buffer);

[[nodiscard]] static constexpr decltype(MessageHeader::payload_size) max_payload_size() {
return calculate_payload_size();
return calculate_payload_size(std::numeric_limits<u8>::max());
}

[[nodiscard]] u8 num_players() const {
return gsl::narrow<u8>(client_identities.size());
}

private:
[[nodiscard]] static constexpr decltype(MessageHeader::payload_size) calculate_payload_size() {
[[nodiscard]] static constexpr decltype(MessageHeader::payload_size) calculate_payload_size(u8 const num_players) {
return static_cast<decltype(MessageHeader::payload_size)>(
sizeof(client_id) + sizeof(start_frame) + sizeof(random_seed) + sizeof(num_players)
sizeof(client_id) + sizeof(start_frame) + sizeof(random_seed) + sizeof(u8) /* num players */
+ num_players * (sizeof(ClientIdentity::client_id) + player_name_buffer_size)
);
}

[[nodiscard]] bool equals(AbstractMessage const& other) const override {
auto const& other_game_start = static_cast<decltype(*this)&>(other);
return std::tie(client_id, start_frame, random_seed, num_players)
return std::tie(client_id, start_frame, random_seed, client_identities)
== std::tie(
other_game_start.client_id,
other_game_start.start_frame,
other_game_start.random_seed,
other_game_start.num_players
other_game_start.client_identities
);
}
};
Expand Down
124 changes: 114 additions & 10 deletions src/network/messages.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <cassert>
#include <cctype>
#include <chrono>
#include <limits>
#include <network/messages.hpp>
Expand All @@ -25,6 +26,8 @@

auto const message_max_payload_size = [message_type] {
switch (message_type) {
case MessageType::Connect:
return Connect::max_payload_size();
case MessageType::Heartbeat:
return Heartbeat::max_payload_size();
case MessageType::GridState:
Expand Down Expand Up @@ -70,6 +73,8 @@

try {
switch (message_type) {
case MessageType::Connect:
return std::make_unique<Connect>(Connect::deserialize(buffer));
case MessageType::Heartbeat:
return std::make_unique<Heartbeat>(Heartbeat::deserialize(buffer));
case MessageType::GridState:
Expand All @@ -87,6 +92,70 @@
std::unreachable();
}

[[nodiscard]] static std::string sanitize(std::string_view const player_name) {
auto sanitized = std::string{};
auto const max_length = std::min(player_name_buffer_size - 1, player_name.length());
for (auto i = usize{ 0 }; i < max_length; ++i) {
auto const c = player_name.at(i);
if (not std::isprint(static_cast<unsigned char>(c))) {
sanitized += '?';
} else {
sanitized += c;
}
}
assert(sanitized.length() < player_name_buffer_size - 1);
return sanitized;
}

Connect::Connect(std::string_view const player_name)
: player_name{ sanitize(player_name) } {}

[[nodiscard]] MessageType Connect::type() const {
return MessageType::Connect;
}

decltype(MessageHeader::payload_size) Connect::payload_size() const {
return max_payload_size();
}

[[nodiscard]] c2k::MessageBuffer Connect::serialize() const {
auto buffer = c2k::MessageBuffer{};
buffer << static_cast<u8>(MessageType::Connect) << payload_size();
auto const expected_message_size = buffer.size() + player_name_buffer_size;
for (auto const c : player_name) {
buffer << c;
}
while (buffer.size() < expected_message_size) {
buffer << '\0';
}
assert(buffer.size() == expected_message_size);
return buffer;
}

[[nodiscard]] Connect Connect::deserialize(c2k::MessageBuffer& buffer) {
static constexpr auto required_num_bytes = max_payload_size(); // Message has a fixed size.
if (buffer.size() < required_num_bytes) {
throw MessageDeserializationError{ std::format(
"too few bytes to deserialize Connect message ({} needed, {} received)",
required_num_bytes,
buffer.size()
) };
}
auto player_name = std::string{};
while (buffer.size() != 0) {
auto const c = buffer.try_extract<char>().value();
if (c == '\0') {
break;
}
player_name += c;
}
return Connect{ sanitize(std::move(player_name)) };
}

[[nodiscard]] bool Connect::equals(AbstractMessage const& other) const {
return other.type() == type() and dynamic_cast<Connect const&>(other).player_name == player_name;
}

[[nodiscard]] MessageType Heartbeat::type() const {
return MessageType::Heartbeat;
}
Expand Down Expand Up @@ -160,19 +229,31 @@
}

[[nodiscard]] decltype(MessageHeader::payload_size) GameStart::payload_size() const {
return calculate_payload_size();
return calculate_payload_size(gsl::narrow<u8>(client_identities.size()));
}

[[nodiscard]] c2k::MessageBuffer GameStart::serialize() const {
auto buffer = c2k::MessageBuffer{};
// clang-format off
buffer << static_cast<std::uint8_t>(MessageType::GameStart)
<< payload_size()
<< client_id
<< start_frame
<< random_seed
<< num_players;
buffer << static_cast<std::uint8_t>(MessageType::GameStart)
<< payload_size()
<< client_id
<< start_frame
<< random_seed
<< gsl::narrow<u8>(client_identities.size());
// clang-format on
for (auto const& [other_client_id, player_name] : client_identities) {
buffer << other_client_id;
auto num_bytes = usize{ 0 };
for (auto const c : player_name) {
buffer << c;
++num_bytes;
}
while (num_bytes < player_name_buffer_size) {
buffer << '\0';
++num_bytes;
}
}
assert(buffer.size() == payload_size() + header_size);
return buffer;
}
Expand All @@ -183,7 +264,7 @@
decltype(client_id),
decltype(start_frame),
decltype(random_seed),
decltype(num_players)
u8
>();
// clang-format on
if (buffer.size() < required_num_bytes) {
Expand All @@ -203,12 +284,35 @@
decltype(GameStart::client_id),
decltype(GameStart::start_frame),
decltype(GameStart::random_seed),
decltype(GameStart::num_players)
u8
>()
.value();
// clang-format on

auto const num_remaining_bytes = num_players * (sizeof(u8) + player_name_buffer_size);
if (buffer.size() < num_remaining_bytes) {
throw MessageDeserializationError{ std::format(
"too few bytes to deserialize client identities within GameStart message ({} needed, {} received)",
num_remaining_bytes,
buffer.size()
) };
}
auto client_identities = std::vector<ClientIdentity>{};
client_identities.reserve(num_players);
for (auto i = decltype(num_players){ 0 }; i < num_players; ++i) {
auto const other_client_id = buffer.try_extract<u8>().value();
auto player_name = std::string{};
for (auto j = usize{ 0 }; j < player_name_buffer_size; ++j) {
auto const c = buffer.try_extract<char>().value();
if (c != '\0') {
player_name += c;
}
}
client_identities.emplace_back(other_client_id, std::move(player_name));
}
assert(buffer.size() == 0);
return GameStart{ client_id, start_frame, random_seed, num_players };

return GameStart{ client_id, start_frame, random_seed, std::move(client_identities) };
}

StateBroadcast::StateBroadcast(std::uint64_t const frame, std::vector<ClientStates> states_per_client)
Expand Down
7 changes: 6 additions & 1 deletion src/obpf/include/obpf/simulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ extern "C" {
};

OBPF_EXPORT struct ObpfTetrion* obpf_create_tetrion(uint64_t seed);
OBPF_EXPORT struct ObpfTetrion* obpf_create_multiplayer_tetrion(const char* host, uint16_t port);
OBPF_EXPORT struct ObpfTetrion* obpf_create_multiplayer_tetrion(
const char* host,
uint16_t port,
const char* player_name
);
OBPF_EXPORT struct ObpfObserverList obpf_tetrion_get_observers(struct ObpfTetrion const* tetrion);
OBPF_EXPORT void obpf_destroy_observers(struct ObpfObserverList observers);
OBPF_EXPORT struct ObpfTetrion* obpf_clone_tetrion(struct ObpfTetrion const* tetrion);
Expand All @@ -71,6 +75,7 @@ extern "C" {
void* user_data
);
OBPF_EXPORT bool obpf_tetrion_is_connected(struct ObpfTetrion const* tetrion);
OBPF_EXPORT char const* obpf_tetrion_player_name(struct ObpfTetrion const* tetrion);
OBPF_EXPORT uint64_t obpf_tetrion_frames_until_game_start(struct ObpfTetrion const* tetrion);
OBPF_EXPORT ObpfStats obpf_tetrion_get_stats(struct ObpfTetrion const* tetrion);
OBPF_EXPORT bool obpf_tetrion_is_game_over(struct ObpfTetrion const* tetrion);
Expand Down
8 changes: 6 additions & 2 deletions src/obpf/simulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ ObpfTetrion* obpf_create_tetrion(uint64_t const seed) try {
return nullptr;
}

struct ObpfTetrion* obpf_create_multiplayer_tetrion(char const* const host, uint16_t const port) try {
auto tetrion = MultiplayerTetrion::create(host, port);
ObpfTetrion* obpf_create_multiplayer_tetrion(char const* const host, uint16_t const port, char const* const player_name) try {
auto tetrion = MultiplayerTetrion::create(host, port, player_name);
if (tetrion == nullptr) {
return nullptr;
}
Expand Down Expand Up @@ -399,6 +399,10 @@ bool obpf_tetrion_is_connected(ObpfTetrion const* tetrion) {
return tetrion->is_connected();
}

char const* obpf_tetrion_player_name(ObpfTetrion const* const tetrion) {
return tetrion->player_name().c_str();
}

std::uint64_t obpf_tetrion_frames_until_game_start(ObpfTetrion const* tetrion) {
return tetrion->frames_until_game_start();
}
Expand Down
Loading