diff options
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/packet.cpp | 38 | ||||
-rw-r--r-- | src/network/packet.h | 4 | ||||
-rw-r--r-- | src/network/room.cpp | 89 | ||||
-rw-r--r-- | src/network/room.h | 21 | ||||
-rw-r--r-- | src/network/room_member.cpp | 128 | ||||
-rw-r--r-- | src/network/room_member.h | 59 |
6 files changed, 300 insertions, 39 deletions
diff --git a/src/network/packet.cpp b/src/network/packet.cpp index 660e92c0d..cc60f2fbc 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp @@ -13,6 +13,18 @@ namespace Network { +#ifndef htonll +u64 htonll(u64 x) { + return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32)); +} +#endif + +#ifndef ntohll +u64 ntohll(u64 x) { + return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32)); +} +#endif + void Packet::Append(const void* in_data, std::size_t size_in_bytes) { if (in_data && (size_in_bytes > 0)) { std::size_t start = data.size(); @@ -100,6 +112,20 @@ Packet& Packet::operator>>(u32& out_data) { return *this; } +Packet& Packet::operator>>(s64& out_data) { + s64 value; + Read(&value, sizeof(value)); + out_data = ntohll(value); + return *this; +} + +Packet& Packet::operator>>(u64& out_data) { + u64 value; + Read(&value, sizeof(value)); + out_data = ntohll(value); + return *this; +} + Packet& Packet::operator>>(float& out_data) { Read(&out_data, sizeof(out_data)); return *this; @@ -183,6 +209,18 @@ Packet& Packet::operator<<(u32 in_data) { return *this; } +Packet& Packet::operator<<(s64 in_data) { + s64 toWrite = htonll(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +Packet& Packet::operator<<(u64 in_data) { + u64 toWrite = htonll(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + Packet& Packet::operator<<(float in_data) { Append(&in_data, sizeof(in_data)); return *this; diff --git a/src/network/packet.h b/src/network/packet.h index 94b351ab1..5a2e58dc2 100644 --- a/src/network/packet.h +++ b/src/network/packet.h @@ -72,6 +72,8 @@ public: Packet& operator>>(u16& out_data); Packet& operator>>(s32& out_data); Packet& operator>>(u32& out_data); + Packet& operator>>(s64& out_data); + Packet& operator>>(u64& out_data); Packet& operator>>(float& out_data); Packet& operator>>(double& out_data); Packet& operator>>(char* out_data); @@ -89,6 +91,8 @@ public: Packet& operator<<(u16 in_data); Packet& operator<<(s32 in_data); Packet& operator<<(u32 in_data); + Packet& operator<<(s64 in_data); + Packet& operator<<(u64 in_data); Packet& operator<<(float in_data); Packet& operator<<(double in_data); Packet& operator<<(const char* in_data); diff --git a/src/network/room.cpp b/src/network/room.cpp index 8b7915bb7..261049ab0 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -4,9 +4,9 @@ #include <algorithm> #include <atomic> +#include <mutex> #include <random> #include <thread> -#include <vector> #include "enet/enet.h" #include "network/packet.h" #include "network/room.h" @@ -19,7 +19,7 @@ static constexpr u32 MaxConcurrentConnections = 10; class Room::RoomImpl { public: // This MAC address is used to generate a 'Nintendo' like Mac address. - const MacAddress NintendoOUI = {0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}; + const MacAddress NintendoOUI; std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress ENetHost* server = nullptr; ///< Network interface. @@ -29,14 +29,17 @@ public: struct Member { std::string nickname; ///< The nickname of the member. - std::string game_name; ///< The current game of the member + GameInfo game_info; ///< The current game of the member MacAddress mac_address; ///< The assigned mac address of the member. ENetPeer* peer; ///< The remote peer. }; using MemberList = std::vector<Member>; - MemberList members; ///< Information about the members of this room. + MemberList members; ///< Information about the members of this room + mutable std::mutex member_mutex; ///< Mutex for locking the members list + /// This should be a std::shared_mutex as soon as C++17 is supported - RoomImpl() : random_gen(std::random_device()()) {} + RoomImpl() + : random_gen(std::random_device()()), NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00} {} /// Thread that receives and dispatches network packets std::unique_ptr<std::thread> room_thread; @@ -146,7 +149,7 @@ void Room::RoomImpl::ServerLoop() { case IdJoinRequest: HandleJoinRequest(&event); break; - case IdSetGameName: + case IdSetGameInfo: HandleGameNamePacket(&event); break; case IdWifiPacket: @@ -212,7 +215,10 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { member.nickname = nickname; member.peer = event->peer; - members.push_back(std::move(member)); + { + std::lock_guard<std::mutex> lock(member_mutex); + members.push_back(std::move(member)); + } // Notify everyone that the room information has changed. BroadcastRoomInformation(); @@ -222,12 +228,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const { // A nickname is valid if it is not already taken by anybody else in the room. // TODO(B3N30): Check for empty names, spaces, etc. + std::lock_guard<std::mutex> lock(member_mutex); return std::all_of(members.begin(), members.end(), [&nickname](const auto& member) { return member.nickname != nickname; }); } bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { // A MAC address is valid if it is not already taken by anybody else in the room. + std::lock_guard<std::mutex> lock(member_mutex); return std::all_of(members.begin(), members.end(), [&address](const auto& member) { return member.mac_address != address; }); } @@ -278,6 +286,7 @@ void Room::RoomImpl::SendCloseMessage() { packet << static_cast<u8>(IdCloseRoom); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + std::lock_guard<std::mutex> lock(member_mutex); for (auto& member : members) { enet_peer_send(member.peer, 0, enet_packet); } @@ -294,10 +303,14 @@ void Room::RoomImpl::BroadcastRoomInformation() { packet << room_information.member_slots; packet << static_cast<u32>(members.size()); - for (const auto& member : members) { - packet << member.nickname; - packet << member.mac_address; - packet << member.game_name; + { + std::lock_guard<std::mutex> lock(member_mutex); + for (const auto& member : members) { + packet << member.nickname; + packet << member.mac_address; + packet << member.game_info.name; + packet << member.game_info.id; + } } ENetPacket* enet_packet = @@ -334,11 +347,13 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { ENET_PACKET_FLAG_RELIABLE); if (destination_address == BroadcastMac) { // Send the data to everyone except the sender + std::lock_guard<std::mutex> lock(member_mutex); for (const auto& member : members) { if (member.peer != event->peer) enet_peer_send(member.peer, 0, enet_packet); } } else { // Send the data only to the destination client + std::lock_guard<std::mutex> lock(member_mutex); auto member = std::find_if(members.begin(), members.end(), [destination_address](const Member& member) -> bool { return member.mac_address == destination_address; @@ -360,6 +375,8 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { auto CompareNetworkAddress = [event](const Member member) -> bool { return member.peer == event->peer; }; + + std::lock_guard<std::mutex> lock(member_mutex); const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress); if (sending_member == members.end()) { return; // Received a chat message from a unknown sender @@ -384,22 +401,32 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { in_packet.Append(event->packet->data, event->packet->dataLength); in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type - std::string game_name; - in_packet >> game_name; - auto member = - std::find_if(members.begin(), members.end(), - [event](const Member& member) -> bool { return member.peer == event->peer; }); - if (member != members.end()) { - member->game_name = game_name; - BroadcastRoomInformation(); + GameInfo game_info; + in_packet >> game_info.name; + in_packet >> game_info.id; + + { + std::lock_guard<std::mutex> lock(member_mutex); + auto member = + std::find_if(members.begin(), members.end(), [event](const Member& member) -> bool { + return member.peer == event->peer; + }); + if (member != members.end()) { + member->game_info = game_info; + } } + BroadcastRoomInformation(); } void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { // Remove the client from the members list. - members.erase(std::remove_if(members.begin(), members.end(), - [client](const Member& member) { return member.peer == client; }), - members.end()); + { + std::lock_guard<std::mutex> lock(member_mutex); + members.erase( + std::remove_if(members.begin(), members.end(), + [client](const Member& member) { return member.peer == client; }), + members.end()); + } // Announce the change to all clients. enet_peer_disconnect(client, 0); @@ -436,6 +463,19 @@ const RoomInformation& Room::GetRoomInformation() const { return room_impl->room_information; } +std::vector<Room::Member> Room::GetRoomMemberList() const { + std::vector<Room::Member> member_list; + std::lock_guard<std::mutex> lock(room_impl->member_mutex); + for (const auto& member_impl : room_impl->members) { + Member member; + member.nickname = member_impl.nickname; + member.mac_address = member_impl.mac_address; + member.game_info = member_impl.game_info; + member_list.push_back(member); + } + return member_list; +}; + void Room::Destroy() { room_impl->state = State::Closed; room_impl->room_thread->join(); @@ -446,7 +486,10 @@ void Room::Destroy() { } room_impl->room_information = {}; room_impl->server = nullptr; - room_impl->members.clear(); + { + std::lock_guard<std::mutex> lock(room_impl->member_mutex); + room_impl->members.clear(); + } room_impl->room_information.member_slots = 0; room_impl->room_information.name.clear(); } diff --git a/src/network/room.h b/src/network/room.h index 54cccf0ae..8285a4d0c 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -7,6 +7,7 @@ #include <array> #include <memory> #include <string> +#include <vector> #include "common/common_types.h" namespace Network { @@ -21,10 +22,15 @@ struct RoomInformation { u32 member_slots; ///< Maximum number of members in this room }; +struct GameInfo { + std::string name{""}; + u64 id{0}; +}; + using MacAddress = std::array<u8, 6>; /// A special MAC address that tells the room we're joining to assign us a MAC address /// automatically. -const MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 802.11 broadcast MAC address constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; @@ -34,7 +40,7 @@ enum RoomMessageTypes : u8 { IdJoinRequest = 1, IdJoinSuccess, IdRoomInformation, - IdSetGameName, + IdSetGameInfo, IdWifiPacket, IdChatMessage, IdNameCollision, @@ -51,6 +57,12 @@ public: Closed, ///< The room is not opened and can not accept connections. }; + struct Member { + std::string nickname; ///< The nickname of the member. + GameInfo game_info; ///< The current game of the member + MacAddress mac_address; ///< The assigned mac address of the member. + }; + Room(); ~Room(); @@ -65,6 +77,11 @@ public: const RoomInformation& GetRoomInformation() const; /** + * Gets a list of the mbmers connected to the room. + */ + std::vector<Member> GetRoomMemberList() const; + + /** * Creates the socket for this room. Will bind to default address if * server is empty string. */ diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index dac9bacae..f229ec6fd 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -5,6 +5,7 @@ #include <atomic> #include <list> #include <mutex> +#include <set> #include <thread> #include "common/assert.h" #include "enet/enet.h" @@ -25,6 +26,9 @@ public: /// Information about the room we're connected to. RoomInformation room_information; + /// The current game name, id and version + GameInfo current_game_info; + std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember. void SetState(const State new_state); bool IsConnected() const; @@ -37,6 +41,24 @@ public: std::unique_ptr<std::thread> loop_thread; std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. std::list<Packet> send_list; ///< A list that stores all packets to send the async + + template <typename T> + using CallbackSet = std::set<CallbackHandle<T>>; + std::mutex callback_mutex; ///< The mutex used for handling callbacks + + class Callbacks { + public: + template <typename T> + CallbackSet<T>& Get(); + + private: + CallbackSet<WifiPacket> callback_set_wifi_packet; + CallbackSet<ChatEntry> callback_set_chat_messages; + CallbackSet<RoomInformation> callback_set_room_information; + CallbackSet<State> callback_set_state; + }; + Callbacks callbacks; ///< All CallbackSets to all events + void MemberLoop(); void StartLoop(); @@ -84,12 +106,20 @@ public: * Disconnects the RoomMember from the Room */ void Disconnect(); + + template <typename T> + void Invoke(const T& data); + + template <typename T> + CallbackHandle<T> Bind(std::function<void(const T&)> callback); }; // RoomMemberImpl void RoomMember::RoomMemberImpl::SetState(const State new_state) { - state = new_state; - // TODO(B3N30): Invoke the callback functions + if (state != new_state) { + state = new_state; + Invoke<State>(state); + } } bool RoomMember::RoomMemberImpl::IsConnected() const { @@ -195,9 +225,10 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev for (auto& member : member_information) { packet >> member.nickname; packet >> member.mac_address; - packet >> member.game_name; + packet >> member.game_info.name; + packet >> member.game_info.id; } - // TODO(B3N30): Invoke callbacks + Invoke(room_information); } void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { @@ -209,7 +240,7 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { // Parse the MAC Address from the packet packet >> mac_address; - // TODO(B3N30): Invoke callbacks + SetState(State::Joined); } void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { @@ -235,7 +266,7 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { packet >> wifi_packet.data; - // TODO(B3N30): Invoke callbacks + Invoke<WifiPacket>(wifi_packet); } void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { @@ -248,7 +279,7 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { ChatEntry chat_entry{}; packet >> chat_entry.nickname; packet >> chat_entry.message; - // TODO(B3N30): Invoke callbacks + Invoke<ChatEntry>(chat_entry); } void RoomMember::RoomMemberImpl::Disconnect() { @@ -276,6 +307,46 @@ void RoomMember::RoomMemberImpl::Disconnect() { server = nullptr; } +template <> +RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_wifi_packet; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>& +RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_state; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>& +RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_room_information; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_chat_messages; +} + +template <typename T> +void RoomMember::RoomMemberImpl::Invoke(const T& data) { + std::lock_guard<std::mutex> lock(callback_mutex); + CallbackSet<T> callback_set = callbacks.Get<T>(); + for (auto const& callback : callback_set) + (*callback)(data); +} + +template <typename T> +RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind( + std::function<void(const T&)> callback) { + std::lock_guard<std::mutex> lock(callback_mutex); + CallbackHandle<T> handle; + handle = std::make_shared<std::function<void(const T&)>>(callback); + callbacks.Get<T>().insert(handle); + return handle; +} + // RoomMember RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} { room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -339,6 +410,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv room_member_impl->SetState(State::Joining); room_member_impl->StartLoop(); room_member_impl->SendJoinRequest(nick, preferred_mac); + SendGameInfo(room_member_impl->current_game_info); } else { room_member_impl->SetState(State::CouldNotConnect); } @@ -366,17 +438,53 @@ void RoomMember::SendChatMessage(const std::string& message) { room_member_impl->Send(std::move(packet)); } -void RoomMember::SendGameName(const std::string& game_name) { +void RoomMember::SendGameInfo(const GameInfo& game_info) { + room_member_impl->current_game_info = game_info; + if (!IsConnected()) + return; + Packet packet; - packet << static_cast<u8>(IdSetGameName); - packet << game_name; + packet << static_cast<u8>(IdSetGameInfo); + packet << game_info.name; + packet << game_info.id; room_member_impl->Send(std::move(packet)); } +RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged( + std::function<void(const RoomMember::State&)> callback) { + return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived( + std::function<void(const WifiPacket&)> callback) { + return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged( + std::function<void(const RoomInformation&)> callback) { + return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved( + std::function<void(const ChatEntry&)> callback) { + return room_member_impl->Bind(callback); +} + +template <typename T> +void RoomMember::Unbind(CallbackHandle<T> handle) { + std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex); + room_member_impl->callbacks.Get<T>().erase(handle); +} + void RoomMember::Leave() { room_member_impl->SetState(State::Idle); room_member_impl->loop_thread->join(); room_member_impl->loop_thread.reset(); } +template void RoomMember::Unbind(CallbackHandle<WifiPacket>); +template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); +template void RoomMember::Unbind(CallbackHandle<RoomInformation>); +template void RoomMember::Unbind(CallbackHandle<ChatEntry>); + } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index bc1af3a7e..98770a234 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -4,6 +4,7 @@ #pragma once +#include <functional> #include <memory> #include <string> #include <vector> @@ -53,12 +54,23 @@ public: struct MemberInformation { std::string nickname; ///< Nickname of the member. - std::string game_name; ///< Name of the game they're currently playing, or empty if they're + GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're /// not playing anything. MacAddress mac_address; ///< MAC address associated with this member. }; using MemberList = std::vector<MemberInformation>; + // The handle for the callback functions + template <typename T> + using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>; + + /** + * Unbinds a callback function from the events. + * @param handle The connection handle to disconnect + */ + template <typename T> + void Unbind(CallbackHandle<T> handle); + RoomMember(); ~RoomMember(); @@ -113,10 +125,49 @@ public: void SendChatMessage(const std::string& message); /** - * Sends the current game name to the room. - * @param game_name The game name. + * Sends the current game info to the room. + * @param game_info The game information. + */ + void SendGameInfo(const GameInfo& game_info); + + /** + * Binds a function to an event that will be triggered every time the State of the member + * changed. The function wil be called every time the event is triggered. The callback function + * must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback); + + /** + * Binds a function to an event that will be triggered every time a WifiPacket is received. + * The function wil be called everytime the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle<WifiPacket> BindOnWifiPacketReceived( + std::function<void(const WifiPacket&)> callback); + + /** + * Binds a function to an event that will be triggered every time the RoomInformation changes. + * The function wil be called every time the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle<RoomInformation> BindOnRoomInformationChanged( + std::function<void(const RoomInformation&)> callback); + + /** + * Binds a function to an event that will be triggered every time a ChatMessage is received. + * The function wil be called every time the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list */ - void SendGameName(const std::string& game_name); + CallbackHandle<ChatEntry> BindOnChatMessageRecieved( + std::function<void(const ChatEntry&)> callback); /** * Leaves the current room. |