summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/network/packet.cpp40
-rw-r--r--src/network/packet.h4
-rw-r--r--src/network/room.cpp89
-rw-r--r--src/network/room.h21
-rw-r--r--src/network/room_member.cpp128
-rw-r--r--src/network/room_member.h59
6 files changed, 301 insertions, 40 deletions
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
index 660e92c0d..7e1a812f3 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;
@@ -195,7 +233,7 @@ Packet& Packet::operator<<(double in_data) {
Packet& Packet::operator<<(const char* in_data) {
// First insert string length
- u32 length = std::strlen(in_data);
+ u32 length = static_cast<u32>(std::strlen(in_data));
*this << length;
// Then insert characters
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.