I currently have a NetworkPublisher/INetworkSubscriber system, where classes can subscribe to packets by some ID (enum, const int, #define, etc) to receive a const RakNet::Packet* from which they can read the relevant data and pass it on to other systems.
The PacketCallback it requires can take a lambda, a member function, etc. The NetworkPublisher is updated by passing it all of the packets received since last frame, which it then passes out to all subscribers based on the first byte of the packet's data.
void ClientList::Subscribe(NetworkPublisher* a_networkPublisher)
{
a_networkPublisher->Register(ID_NEW_INCOMING_CONNECTION, PacketCallback(&ClientList::OnClientConnected, this));
a_networkPublisher->Register(ID_CONNECTION_LOST, PacketCallback(&ClientList::OnClientDisconnected, this));
a_networkPublisher->Register(ID_DISCONNECTION_NOTIFICATION, PacketCallback(&ClientList::OnClientDisconnected, this));
}
I then have a system of StaticEvents to which anything can subscribe. This is ClientList::OnClientConnected firing off one of those events:
void ClientList::OnClientConnected(const RakNet::Packet* a_packet)
{
auto id = m_nextClientID++;
m_addresses.emplace(id, a_packet->systemAddress);
m_connectedClients.push_back(NetClient(id));
printf("Client connected: %s\n", a_packet->systemAddress.ToString(true));
SystemEvents::ClientCreated.Invoke(id);
}
GamePacketHandler then acts as a mediator between these network-related layers and the GameFramework and its components:
GamePacketHandler::GamePacketHandler(GameFramework* a_gameFramework)
: m_gameFramework(a_gameFramework)
{
SystemEvents::ClientCreated.Subscribe([this](NetClient::ClientID a_clientID) {
auto& player = m_gameFramework->GetPlayerList()->CreatePlayer(a_clientID);
// send sync here for now
RakNet::BitStream bs;
bs.Write((unsigned char)EPacketType::SERVER_INITIAL_SYNC);
bs.Write(player.GetID());
bs.Write(999); // num other ships
m_gameFramework->GetNetworkMessenger()->SendToPlayer(player.GetID(), bs, HIGH_PRIORITY, UNRELIABLE);
});
SystemEvents::ClientDestroyed.Subscribe([this](NetClient::ClientID a_clientID) {
auto playerID = m_gameFramework->GetPlayerList()->ClientIDToPlayerID(a_clientID);
m_gameFramework->GetPlayerList()->DestroyPlayer(playerID);
});
}
What kind of layout do you use for this stuff? I don't want the packets and network system to be directly handled by gameplay, so this mediator + publisher/subscriber pattern seems to accomplish what I'm after right now. Not sure of how practical it will be as things can larger, although I could break the mediators out into a separate class per 'area' of the game so there's fewer 'god' classes. Still a bit interconnected, though, esp. in the GameFramework (which would need to hold each of these mediators and give them a pointer to itself).
Let me know your thoughts!