forked from mirrors/0ad
# Refactored the networking code, and redesigned the game setup screen.
Major updates to most network classes. Simplify CNetServer so it doesn't duplicate any client behaviour; all players now run CNetClient. Remove most player/slot management from networking code. Wait for all players to finish loading before starting the simulation. Remove CGameAttributes; attributes are now just a JS object. Remove CPlayer; they are now just simulation entities. Handle player colours via simulation system. Add a default map for Atlas, so it always has something to load. Move network documentation to Doxygen. Remove lots of now-unused code. This was SVN commit r7653.
This commit is contained in:
+27
-425
@@ -16,364 +16,40 @@
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
#include "NetHost.h"
|
||||
#include "NetSession.h"
|
||||
#include "NetMessage.h"
|
||||
|
||||
#include "NetHost.h"
|
||||
|
||||
#include "network/NetMessage.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
static const int ENET_DEFAULT_CHANNEL = 0;
|
||||
static const int CONNECT_TIMEOUT = 5000;
|
||||
static const int DISCONNECT_TIMEOUT = 1000;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetHost()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetHost::CNetHost(ScriptInterface& scriptInterface) :
|
||||
m_ScriptInterface(scriptInterface)
|
||||
bool CNetHost::SendMessage(const CNetMessage* message, ENetPeer* peer, const char* peerName)
|
||||
{
|
||||
m_Host = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CNetHost()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetHost::~CNetHost()
|
||||
{
|
||||
// Shutdown(); // TODO: should do something like this except don't call HandleDisconnect()
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Create()
|
||||
// Desc: Creates a client host
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Create()
|
||||
{
|
||||
debug_assert(!m_Host);
|
||||
|
||||
// Create ENet host
|
||||
m_Host = enet_host_create(NULL, 1, 0, 0);
|
||||
if (!m_Host)
|
||||
ENetPacket* packet = CreatePacket(message);
|
||||
if (!packet)
|
||||
return false;
|
||||
|
||||
LOGMESSAGE(L"Net: Sending message %hs of size %lu to %s", message->ToString().c_str(), (unsigned long)packet->dataLength, peerName);
|
||||
|
||||
// Let ENet send the message to peer
|
||||
if (enet_peer_send(peer, DEFAULT_CHANNEL, packet) < 0)
|
||||
{
|
||||
LOGERROR(L"Net: Failed to send packet to peer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't call enet_host_flush now - let it queue up all the packets
|
||||
// and send them during the next frame
|
||||
//
|
||||
// TODO: we should flush explicitly at some appropriate point before the next frame
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Create()
|
||||
// Desc: Creates a server host
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Create(u16 port, size_t maxPeers)
|
||||
ENetPacket* CNetHost::CreatePacket(const CNetMessage* message)
|
||||
{
|
||||
ENetAddress addr;
|
||||
|
||||
// Bind to default host
|
||||
addr.host = ENET_HOST_ANY;
|
||||
addr.port = port;
|
||||
|
||||
// Create ENet server
|
||||
m_Host = enet_host_create(&addr, maxPeers, 0, 0);
|
||||
if (!m_Host)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Shutdown()
|
||||
// Desc: Shuts down network server and releases any resources
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetHost::Shutdown()
|
||||
{
|
||||
// Disconnect and release each peer
|
||||
PeerSessionList::iterator it = m_PeerSessions.begin();
|
||||
for (; it != m_PeerSessions.end(); it++)
|
||||
{
|
||||
if (!it->pSession)
|
||||
continue;
|
||||
|
||||
Disconnect(it->pSession);
|
||||
|
||||
delete it->pSession;
|
||||
}
|
||||
|
||||
m_PeerSessions.clear();
|
||||
|
||||
// Destroy server
|
||||
if (m_Host)
|
||||
enet_host_destroy(m_Host);
|
||||
|
||||
m_Host = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Connect()
|
||||
// Desc: Connects to the specified remote host
|
||||
// Note: Only clients use this method for connection to server
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Connect(const CStr& host, u16 port)
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
|
||||
// Bind to specified host
|
||||
ENetAddress addr;
|
||||
addr.port = port;
|
||||
if (enet_address_set_host(&addr, host.c_str()) < 0)
|
||||
return false;
|
||||
|
||||
// Initiate connection, allocate one channel
|
||||
ENetPeer* pPeer = enet_host_connect(m_Host, &addr, 1);
|
||||
if (!pPeer)
|
||||
return false;
|
||||
|
||||
// Wait a few seconds for the connection to succeed
|
||||
// TODO: we ought to poll asynchronously so we can update the GUI while waiting
|
||||
ENetEvent event;
|
||||
if (enet_host_service(m_Host, &event, CONNECT_TIMEOUT) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
|
||||
{
|
||||
// Connection succeeded
|
||||
CNetSession* pNewSession = new CNetSession(this, event.peer);
|
||||
|
||||
if (!SetupSession(pNewSession))
|
||||
{
|
||||
delete pNewSession;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGMESSAGE(L"Net: Successfully connected to server %hs:%d", host.c_str(), port);
|
||||
|
||||
// Successfully handled?
|
||||
if (!HandleConnect(pNewSession))
|
||||
{
|
||||
delete pNewSession;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store the only server session
|
||||
PeerSession item;
|
||||
item.pPeer = event.peer;
|
||||
item.pSession = pNewSession;
|
||||
m_PeerSessions.push_back(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGERROR(L"Net: Connection to server %hs:%d failed", host.c_str(), port);
|
||||
|
||||
// Timed out or a host was disconnected
|
||||
enet_peer_reset(pPeer);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ConnectAsync()
|
||||
// Desc: Connects to the specified remote host
|
||||
// Note: Only clients use this method for connection to server
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::ConnectAsync(const CStr& host, u16 port)
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
|
||||
// Bind to specified host
|
||||
ENetAddress addr;
|
||||
addr.port = port;
|
||||
if (enet_address_set_host(&addr, host.c_str()) < 0)
|
||||
return false;
|
||||
|
||||
// Initiate connection, allocate one channel
|
||||
ENetPeer* pPeer = enet_host_connect(m_Host, &addr, 1);
|
||||
if (!pPeer)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Disconnect()
|
||||
// Desc: Disconnects the specified session from the host
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Disconnect(CNetSession* pSession)
|
||||
{
|
||||
// Validate parameters
|
||||
if (!pSession)
|
||||
return false;
|
||||
|
||||
debug_assert(m_Host);
|
||||
debug_assert(pSession->m_Peer);
|
||||
|
||||
// Disconnect peer
|
||||
enet_peer_disconnect(pSession->m_Peer, 0);
|
||||
|
||||
// Allow a few seconds for the disconnect to succeed
|
||||
ENetEvent event;
|
||||
while (enet_host_service(m_Host, &event, DISCONNECT_TIMEOUT) > 0)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
|
||||
// Drop any received packets
|
||||
enet_packet_destroy(event.packet);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
|
||||
// Disconnect received for peer
|
||||
if (!HandleDisconnect(pSession))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect attempt didn't succeed, force connection down
|
||||
enet_peer_reset(pSession->m_Peer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ProcessEvents()
|
||||
// Desc: Wait for events and shuttles packets between the host and its peers
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetHost::Poll()
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
|
||||
// Poll host for events
|
||||
ENetEvent event;
|
||||
while (enet_host_service(m_Host, &event, 0) > 0)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
{
|
||||
// A new client has connected, handle it
|
||||
CNetSession* pSession = new CNetSession(this, event.peer);
|
||||
|
||||
// Setup new session
|
||||
if (!SetupSession(pSession))
|
||||
{
|
||||
delete pSession;
|
||||
break;
|
||||
}
|
||||
|
||||
LOGMESSAGE(L"Net: A new client connected from %x:%u", event.peer->address.host, event.peer->address.port);
|
||||
|
||||
// Successfully handled?
|
||||
if (!HandleConnect(pSession))
|
||||
{
|
||||
delete pSession;
|
||||
break;
|
||||
}
|
||||
|
||||
event.peer->data = pSession;
|
||||
|
||||
// Add new item to internal list
|
||||
PeerSession item;
|
||||
item.pPeer = event.peer;
|
||||
item.pSession = pSession;
|
||||
m_PeerSessions.push_back( item );
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
{
|
||||
// Client has disconnected, handle it
|
||||
PeerSessionList::iterator it = m_PeerSessions.begin();
|
||||
for (; it != m_PeerSessions.end(); it++)
|
||||
{
|
||||
// Is this our session?
|
||||
if (it->pPeer == event.peer)
|
||||
{
|
||||
LOGMESSAGE(L"Net: %p disconnected", event.peer->data);
|
||||
|
||||
// Successfully handled?
|
||||
if (HandleDisconnect(it->pSession))
|
||||
m_PeerSessions.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
{
|
||||
// A new data packet was received from client, handle message
|
||||
PeerSessionList::iterator it = m_PeerSessions.begin();
|
||||
for (; it != m_PeerSessions.end(); it++)
|
||||
{
|
||||
// Is this our session?
|
||||
if (it->pPeer == event.peer)
|
||||
{
|
||||
bool ok = false;
|
||||
|
||||
// Create message from raw data
|
||||
CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, m_ScriptInterface);
|
||||
if (pNewMessage)
|
||||
{
|
||||
LOGMESSAGE(L"Message %hs of size %lu was received from %p", pNewMessage->ToString().c_str(), (unsigned long)pNewMessage->GetSerializedLength(), event.peer->data);
|
||||
|
||||
ok = HandleMessageReceive(pNewMessage, it->pSession);
|
||||
|
||||
delete pNewMessage;
|
||||
}
|
||||
|
||||
// Done using the packet
|
||||
enet_packet_destroy(event.packet);
|
||||
|
||||
// TODO: what should we do if ok is false?
|
||||
// For now, just carry on as if nothing bad happened
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Broadcast()
|
||||
// Desc: Broadcast the specified message to connected clients
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetHost::Broadcast(const CNetMessage* pMessage)
|
||||
{
|
||||
// Validate parameters
|
||||
if (!pMessage)
|
||||
return;
|
||||
|
||||
// Loop through the list of sessions and send the message to each
|
||||
for (uint i = 0; i < GetSessionCount(); i++)
|
||||
{
|
||||
CNetSession* pCurrSession = GetSession(i);
|
||||
if (!pCurrSession)
|
||||
continue;
|
||||
|
||||
SendMessage(pCurrSession, pMessage);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SendMessage()
|
||||
// Desc: Sends the specified message to peer
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::SendMessage(const CNetSession* pSession, const CNetMessage* pMessage)
|
||||
{
|
||||
// Validate parameters
|
||||
if (!pMessage || !pSession)
|
||||
return false;
|
||||
|
||||
debug_assert(pSession->m_Peer);
|
||||
debug_assert(m_Host);
|
||||
|
||||
size_t size = pMessage->GetSerializedLength();
|
||||
size_t size = message->GetSerializedLength();
|
||||
|
||||
debug_assert(size); // else we'll fail when accessing the 0th element
|
||||
|
||||
@@ -382,86 +58,12 @@ bool CNetHost::SendMessage(const CNetSession* pSession, const CNetMessage* pMess
|
||||
buffer.resize(size);
|
||||
|
||||
// Save message to internal buffer
|
||||
pMessage->Serialize(&buffer[0]);
|
||||
message->Serialize(&buffer[0]);
|
||||
|
||||
// Create a reliable packet
|
||||
ENetPacket* pPacket = enet_packet_create(&buffer[0], size, ENET_PACKET_FLAG_RELIABLE);
|
||||
if (!pPacket)
|
||||
return false;
|
||||
ENetPacket* packet = enet_packet_create(&buffer[0], size, ENET_PACKET_FLAG_RELIABLE);
|
||||
if (!packet)
|
||||
LOGERROR(L"Net: Failed to construct packet");
|
||||
|
||||
// Let ENet send the message to peer
|
||||
if (enet_peer_send(pSession->m_Peer, ENET_DEFAULT_CHANNEL, pPacket) < 0)
|
||||
{
|
||||
// ENet failed to send the packet
|
||||
LOGERROR(L"Net: Failed to send ENet packet to peer");
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGMESSAGE(L"Net: Message %hs of size %lu was sent to %p",
|
||||
pMessage->ToString().c_str(), (unsigned long)size, pSession->m_Peer->data);
|
||||
}
|
||||
|
||||
// Don't call enet_host_flush - let it queue up all the packets
|
||||
// and send them during the next frame
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ReceiveMessage()
|
||||
// Desc: Receives a message from client if incoming packets are available
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage* CNetHost::ReceiveMessage(const CNetSession* pSession)
|
||||
{
|
||||
// Validate parameters
|
||||
if (!pSession)
|
||||
return NULL;
|
||||
|
||||
debug_assert(pSession->m_Peer);
|
||||
|
||||
// Let ENet receive a message from peer
|
||||
ENetPacket* pPacket = enet_peer_receive(pSession->m_Peer, ENET_DEFAULT_CHANNEL);
|
||||
if (!pPacket)
|
||||
return NULL;
|
||||
|
||||
// Create new message
|
||||
return CNetMessageFactory::CreateMessage(pPacket->data, pPacket->dataLength, m_ScriptInterface);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleMessageReceive()
|
||||
// Desc: Allow application to handle message recive
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::HandleMessageReceive(CNetMessage* pMessage, CNetSession* pSession)
|
||||
{
|
||||
// Validate parameters
|
||||
if (!pSession || !pMessage)
|
||||
return false;
|
||||
|
||||
// Update FSM
|
||||
return pSession->Update(pMessage->GetType(), pMessage);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetSessionCount()
|
||||
// Desc: Returns the number of sessions the host manages
|
||||
//-----------------------------------------------------------------------------
|
||||
size_t CNetHost::GetSessionCount() const
|
||||
{
|
||||
return m_PeerSessions.size();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetSession()
|
||||
// Desc: Rteurns the session for the index
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetSession* CNetHost::GetSession(size_t index)
|
||||
{
|
||||
// Validate parameter
|
||||
if (index >= GetSessionCount())
|
||||
return NULL;
|
||||
|
||||
return m_PeerSessions[index].pSession;
|
||||
return packet;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user