Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #ifndef ROBOTS_SERIALIZE_HPP
- #define ROBOTS_SERIALIZE_HPP
- #include <boost/endian/conversion.hpp>
- #include <concepts>
- #include <cstddef>
- #include <iostream>
- #include <map>
- #include <ranges>
- #include <set>
- #include <string>
- #include <type_traits>
- #include <utility>
- #include <variant>
- #include <vector>
- #include "robots-reader.hpp"
- // Integral types used in messages.
- using message_id_t = uint8_t;
- using player_id_t = uint8_t;
- using bomb_id_t = uint32_t;
- using score_t = uint32_t;
- // Position on the board - pair of coordinates.
- using Position = std::pair<uint16_t, uint16_t>;
- // Bomb description - position and timer.
- using Bomb = std::pair<Position, uint16_t>;
- // Player description - name and address.
- using Player = std::pair<std::string, std::string>;
- // Game event types:
- // BombPlaced event: <id, position>
- using BombPlaced = std::tuple<bomb_id_t, Position>;
- // BombExploded event: <id, robots_destroyed, blocks_destroyed>
- using BombExploded = std::tuple<bomb_id_t, std::vector<player_id_t>, std::vector<Position>>;
- // PlayerMoved event: <id, position>
- using PlayerMoved = std::tuple<player_id_t, Position>;
- // BlockPlaced event: <position>
- using BlockPlaced = Position;
- // Game event type.
- using Event = std::variant<BombPlaced, BombExploded, PlayerMoved, BlockPlaced>;
- // Server information.
- struct ServerInfo {
- std::string server_name;
- uint16_t size_x;
- uint16_t size_y;
- uint16_t game_length;
- };
- // Players information.
- struct PlayersInfo {
- std::map<player_id_t, Player> players;
- };
- // Lobby specific information.
- struct LobbyInfo {
- uint8_t players_count;
- uint16_t explosion_radius;
- uint16_t bomb_timer;
- };
- // In-game specific information.
- struct GameInfo {
- uint16_t turn;
- std::map<player_id_t, Position> player_positions;
- std::set<Position> blocks;
- std::vector<Bomb> bombs;
- std::set<Position> explosions;
- std::map<player_id_t, score_t> scores;
- };
- // Server to client message types:
- // Hello message:
- // <server name, players count, size x, size y, game length, explosion radius, bomb timer>.
- struct Hello : ServerInfo, LobbyInfo {};
- // AcceptedPlayer message: <id, player>.
- using AcceptedPlayer = std::tuple<player_id_t, Player>;
- // GameStarted message: <players>.
- using GameStarted = std::map<player_id_t, Player>;
- // Turn message: <turn, events>.
- using Turn = std::tuple<uint16_t, std::vector<Event>>;
- // GameEnded message: <scores>.
- using GameEnded = std::map<player_id_t, score_t>;
- // Server to client message type.
- using ServerMessage = std::variant<Hello, AcceptedPlayer, GameStarted, Turn, GameEnded>;
- // Direction message types.
- using Up = struct {};
- using Right = struct {};
- using Down = struct {};
- using Left = struct {};
- using Direction = std::variant<Up, Right, Down, Left>;
- // Movement message types.
- using Join = std::string;
- using PlaceBomb = struct {};
- using PlaceBlock = struct {};
- using Move = Direction;
- // Client to server message type.
- using ClientMessage = std::variant<Join, PlaceBomb, PlaceBlock, Move>;
- // GUI to client message type.
- using InputMessage = std::variant<PlaceBomb, PlaceBlock, Move>;
- // Game state used by client to keep track on the game.
- struct GameState : ServerInfo, PlayersInfo, LobbyInfo, GameInfo {
- bool in_game = false;
- std::map<bomb_id_t, Bomb> bomb_map;
- std::set<player_id_t> robots_killed;
- std::set<Position> previous_blocks;
- };
- // Client to GUI message types:
- // Client to GUI message for lobby state.
- struct Lobby : ServerInfo, PlayersInfo, LobbyInfo {
- Lobby() = default;
- explicit Lobby(const GameState &g) : ServerInfo(g), PlayersInfo(g), LobbyInfo(g) {}
- };
- // Client to GUI message for game state.
- struct Game : ServerInfo, PlayersInfo, GameInfo {
- Game() = default;
- explicit Game(const GameState &g) : ServerInfo(g), PlayersInfo(g), GameInfo(g) {}
- };
- //Client to GUI message type.
- using DrawMessage = std::variant<Lobby, Game>;
- ClientMessage client_from_gui_message(const InputMessage &m) {
- return std::visit([]<typename T>(const T &a) { return ClientMessage{a}; }, m);
- }
- template<typename T>
- using range_size_t =
- typename std::conditional<std::is_same_v<T, std::string>, uint8_t, uint32_t>::type;
- // Check if a type is same as std::pair of any types.
- template<typename T>
- concept is_pair = requires(T a) {
- { a.first } -> std::same_as<typename T::first_type &>;
- { a.second } -> std::same_as<typename T::second_type &>;
- };
- // Check if type has all fields of Game structure.
- template<typename T>
- concept is_lobby_transformable = requires(T a) {
- a.server_name;
- a.players_count;
- a.size_x;
- a.size_y;
- a.game_length;
- a.explosion_radius;
- a.bomb_timer;
- a.players;
- };
- // Check if type has all fields of Game structure.
- template<typename T>
- concept is_game_transformable = requires(T a) {
- a.server_name;
- a.size_x;
- a.size_y;
- a.game_length;
- a.turn;
- a.players;
- a.player_positions;
- a.blocks;
- a.bombs;
- a.explosions;
- a.scores;
- };
- // Serializer class.
- // Implements host to network serialization for a number of data types which may be members of
- // client messages.
- class Serializer {
- // data always saved in serialized way
- std::vector<uint8_t> data;
- public:
- // Util function.
- void clear() { data = std::vector<uint8_t>{}; }
- // Util function.
- void print() const {
- for (auto byte: data) printf("%u,", byte);
- std::cout << std::endl;
- }
- // Extract data from serializer. Clear.
- Serializer &operator>>(std::vector<uint8_t> &v) {
- v = std::move(data);
- clear();
- return *this;
- }
- std::vector<uint8_t> get() {
- std::vector<uint8_t> result{std::move(data)};
- clear();
- return result;
- }
- // Serialize an integral type.
- // Convert the argument to network order if needed, append it to the data vector byte by byte.
- template<std::integral T>
- void serialize(T i) requires(!std::is_enum_v<T>) {
- boost::endian::native_to_big_inplace(i);
- uint8_t bytes[sizeof(T)];
- *reinterpret_cast<T *>(bytes) = i;
- for (auto byte: bytes) data.push_back(byte);
- }
- // Serialize a sized range.
- // Serialize its size as 4 byte unsigned (1 byte for string) and then serialize all its
- // elements one after another.
- template<std::ranges::sized_range T>
- void serialize(const T &r) {
- serialize(static_cast<range_size_t<T>>(std::ranges::size(r)));
- for (const auto &e: r) *this << e;
- }
- // Serialize a pair.
- // Serialize it as a tuple.
- template<typename... Ts>
- void serialize(const std::pair<Ts...> &p) {
- *this << p.first << p.second;
- }
- // Serialize a tuple.
- // Serialize its elements one after another.
- template<typename... Ts>
- void serialize(const std::tuple<Ts...> &t) {
- std::apply([this](const auto &...i) { ((*this << i), ...); }, t);
- }
- // Serialize a variant.
- // Serialize the held alternative's index as 1 byte unsigned and then serialize the underlying
- // data type.
- template<typename... Ts>
- void serialize(const std::variant<Ts...> &v) {
- auto index = static_cast<message_id_t>(v.index());
- serialize(index);
- std::visit([this](const auto &i) { *this << i; }, v);
- }
- // Serialize a Lobby structure.
- // Serialize it in order given by GUI interface.
- void serialize(const Lobby &l) {
- *this << l.server_name << l.players_count << l.size_x << l.size_y << l.game_length
- << l.explosion_radius << l.bomb_timer << l.players;
- }
- // Serialize a Game structure.
- // Serialize it in order given by GUI interface.
- void serialize(const Game &g) {
- *this << g.server_name << g.size_x << g.size_y << g.game_length << g.turn << g.players
- << g.player_positions << g.blocks << g.bombs << g.explosions << g.scores;
- }
- // Serialize a GameState as DrawMessage of Game or Lobby type according to in_game value.
- void serialize(const GameState &g) {
- if (g.in_game) *this << DrawMessage{Game{g}};
- else
- *this << DrawMessage{Lobby{g}};
- }
- // Serialize an empty structure.
- // Empty structures are used to automate the serialization by using std::variants, they are not
- // converted to any bytes.
- template<typename T>
- requires(std::is_empty_v<T>) void serialize(const T &) {}
- // Insertion operator.
- // Serialize the argument, return this.
- template<typename T>
- Serializer &operator<<(T i) {
- serialize(i);
- return *this;
- }
- };
- class DeserializationError : public std::runtime_error {
- static const char *deserialization_error_message;
- public:
- explicit DeserializationError(const std::string &message)
- : std::runtime_error(deserialization_error_message + message) {}
- };
- const char *DeserializationError::deserialization_error_message{"Deserialization error: "};
- template<Reader R>
- class Deserializer {
- R &reader;
- // Utility template used for generalizing std::ranges::range deserialization.
- // If T is same type as std::pair, remove const qualifier form types inside this pair.
- template<bool is_pair, typename T>
- struct remove_cv_pair {
- using type = std::pair<std::remove_cv_t<typename T::first_type>,
- std::remove_cv_t<typename T::second_type>>;
- };
- // If T is not std::pair type, don't change anything.
- template<typename T>
- struct remove_cv_pair<false, T> {
- using type = T;
- };
- // Construct any variant from given index.
- // Idea for function from:
- // https://stackoverflow.com/questions/60564132/default-constructing-an-stdvariant-from-index
- template<typename T, std::size_t I = 0>
- T variant_from_index(std::size_t index) {
- if constexpr (I >= std::variant_size_v<T>)
- throw ::std::runtime_error{"Variant index out of bounds"};
- else
- return index == 0 ? T{std::in_place_index<I>} : variant_from_index<T, I + 1>(index - 1);
- }
- public:
- explicit Deserializer(R &r) : reader{r} {}
- // Deserialize an integral type.
- // Convert the argument from network order to host order if needed, append it to the data
- // vector byte by byte.
- template<std::integral T>
- void deserialize(T &i) requires(!std::is_enum_v<T>) {
- try {
- std::vector<uint8_t> bytes = reader.read(sizeof(T));
- i = boost::endian::big_to_native(*reinterpret_cast<T *>(bytes.data()));
- } catch (std::exception &e) { throw DeserializationError{e.what()}; }
- }
- // Deserialize a pair.
- // Deserialize it as a tuple.
- template<typename T1, typename T2>
- void deserialize(std::pair<T1, T2> &p) {
- *this >> p.first >> p.second;
- }
- // Deserialize a tuple.
- // Deserialize its elements one after another.
- template<typename... Ts>
- void deserialize(std::tuple<Ts...> &t) {
- std::apply([this](auto &...i) { ((*this >> i), ...); }, t);
- }
- // Deserialize a sized range.
- // Deserialize its size as 4 byte unsigned (1 byte for string) and then deserialize all its
- // elements one after another.
- template<std::ranges::range T>
- void deserialize(T &r) {
- range_size_t<T> size;
- using value_t = std::remove_cvref_t<typename T::iterator::value_type>;
- using E = typename remove_cv_pair<is_pair<value_t>, value_t>::type;
- deserialize(size);
- for (; size > 0; size--) {
- E element;
- *this >> element;
- r.insert(r.end(), element);
- }
- }
- // Deserialize a variant.
- // Deserialize the held alternative's index as 1 byte unsigned and then deserialize the
- // underlying data type.
- template<typename... Ts>
- void deserialize(std::variant<Ts...> &v) {
- message_id_t index;
- std::cout << "Trying to deserialize variant: \n";
- deserialize(index);
- std::cout << "Deserializing variant of ID: " << int(index) << std::endl;
- try {
- v = variant_from_index<std::variant<Ts...>>(index);
- } catch (std::exception &e) { throw DeserializationError("Unknown message id."); }
- std::visit([this](auto &i) { *this >> i; }, v);
- if constexpr (std::is_same_v<std::variant<Ts...>, InputMessage>) {
- if (!reader.empty()) { throw DeserializationError("Trailing bytes in UDP message!"); }
- }
- }
- // Deserialize an empty structure.
- // Empty structures are used to automate the serialization by using variants, they are not
- // converted to any bytes.
- template<typename T>
- void deserialize(T &) requires(std::is_empty_v<T>) {}
- // Deserialize Hello structure.
- void deserialize(Hello &h) {
- *this >> h.server_name >> h.players_count >> h.size_x >> h.size_y >> h.game_length >>
- h.explosion_radius >> h.bomb_timer;
- }
- // Extraction operator.
- // Deserialize the argument, return this.
- template<typename T>
- Deserializer &operator>>(T &i) {
- deserialize(i);
- return *this;
- }
- };
- #endif//ROBOTS_SERIALIZE_HPP
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement