Network/ENet: Add peer-side lag simulation

This commit is contained in:
Lynix 2017-02-20 23:37:31 +01:00
parent cc57fed42e
commit b0d0a63fca
6 changed files with 104 additions and 14 deletions

View File

@ -100,13 +100,20 @@ namespace Nz
static bool Initialize(); static bool Initialize();
static void Uninitialize(); static void Uninitialize();
struct PendingPacket struct PendingIncomingPacket
{ {
IpAddress from; IpAddress from;
NetPacket data; NetPacket data;
UInt32 deliveryTime; UInt32 deliveryTime;
}; };
struct PendingOutgoingPacket
{
IpAddress to;
NetPacket data;
UInt32 deliveryTime;
};
std::array<ENetProtocol, ENetConstants::ENetProtocol_MaximumPacketCommands> m_commands; std::array<ENetProtocol, ENetConstants::ENetProtocol_MaximumPacketCommands> m_commands;
std::array<NetBuffer, ENetConstants::ENetProtocol_MaximumPacketCommands * 2 + 1> m_buffers; std::array<NetBuffer, ENetConstants::ENetProtocol_MaximumPacketCommands * 2 + 1> m_buffers;
std::array<UInt8, ENetConstants::ENetProtocol_MaximumMTU> m_packetData[2]; std::array<UInt8, ENetConstants::ENetProtocol_MaximumMTU> m_packetData[2];
@ -123,7 +130,8 @@ namespace Nz
std::size_t m_receivedDataLength; std::size_t m_receivedDataLength;
std::uniform_int_distribution<UInt16> m_packetDelayDistribution; std::uniform_int_distribution<UInt16> m_packetDelayDistribution;
std::vector<ENetPeer> m_peers; std::vector<ENetPeer> m_peers;
std::vector<PendingPacket> m_pendingPackets; std::vector<PendingIncomingPacket> m_pendingIncomingPackets;
std::vector<PendingOutgoingPacket> m_pendingOutgoingPackets;
UInt8* m_receivedData; UInt8* m_receivedData;
Bitset<UInt64> m_dispatchQueue; Bitset<UInt64> m_dispatchQueue;
MemoryPool m_packetPool; MemoryPool m_packetPool;

View File

@ -27,6 +27,7 @@
#include <Nazara/Network/UdpSocket.hpp> #include <Nazara/Network/UdpSocket.hpp>
#include <array> #include <array>
#include <list> #include <list>
#include <random>
#include <vector> #include <vector>
namespace Nz namespace Nz
@ -59,6 +60,7 @@ namespace Nz
inline bool HasPendingCommands(); inline bool HasPendingCommands();
inline bool IsConnected() const; inline bool IsConnected() const;
inline bool IsSimulationEnabled() const;
void Ping(); void Ping();
@ -68,6 +70,8 @@ namespace Nz
bool Send(UInt8 channelId, ENetPacketRef packetRef); bool Send(UInt8 channelId, ENetPacketRef packetRef);
bool Send(UInt8 channelId, ENetPacketFlags flags, NetPacket&& packet); bool Send(UInt8 channelId, ENetPacketFlags flags, NetPacket&& packet);
void SimulateNetwork(double packetLossProbability, UInt16 minDelay, UInt16 maxDelay);
void ThrottleConfigure(UInt32 interval, UInt32 acceleration, UInt32 deceleration); void ThrottleConfigure(UInt32 interval, UInt32 acceleration, UInt32 deceleration);
ENetPeer& operator=(const ENetPeer&) = delete; ENetPeer& operator=(const ENetPeer&) = delete;
@ -177,12 +181,14 @@ namespace Nz
ENetHost* m_host; ENetHost* m_host;
IpAddress m_address; /**< Internet address of the peer */ IpAddress m_address; /**< Internet address of the peer */
std::array<UInt32, unsequencedWindow> m_unsequencedWindow; std::array<UInt32, unsequencedWindow> m_unsequencedWindow;
std::bernoulli_distribution m_packetLossProbability;
std::list<IncomingCommmand> m_dispatchedCommands; std::list<IncomingCommmand> m_dispatchedCommands;
std::list<OutgoingCommand> m_outgoingReliableCommands; std::list<OutgoingCommand> m_outgoingReliableCommands;
std::list<OutgoingCommand> m_outgoingUnreliableCommands; std::list<OutgoingCommand> m_outgoingUnreliableCommands;
std::list<OutgoingCommand> m_sentReliableCommands; std::list<OutgoingCommand> m_sentReliableCommands;
std::list<OutgoingCommand> m_sentUnreliableCommands; std::list<OutgoingCommand> m_sentUnreliableCommands;
std::size_t m_totalWaitingData; std::size_t m_totalWaitingData;
std::uniform_int_distribution<UInt16> m_packetDelayDistribution;
std::vector<Acknowledgement> m_acknowledgements; std::vector<Acknowledgement> m_acknowledgements;
std::vector<Channel> m_channels; std::vector<Channel> m_channels;
ENetPeerState m_state; ENetPeerState m_state;
@ -232,6 +238,7 @@ namespace Nz
UInt32 m_windowSize; UInt32 m_windowSize;
UInt64 m_totalPacketLost; UInt64 m_totalPacketLost;
UInt64 m_totalPacketSent; UInt64 m_totalPacketSent;
bool m_isSimulationEnabled;
}; };
} }

View File

@ -12,7 +12,8 @@ namespace Nz
m_host(host), m_host(host),
m_incomingSessionID(0xFF), m_incomingSessionID(0xFF),
m_outgoingSessionID(0xFF), m_outgoingSessionID(0xFF),
m_incomingPeerID(peerId) m_incomingPeerID(peerId),
m_isSimulationEnabled(false)
{ {
Reset(); Reset();
} }
@ -62,6 +63,11 @@ namespace Nz
return m_state == ENetPeerState::Connected || m_state == ENetPeerState::DisconnectLater; return m_state == ENetPeerState::Connected || m_state == ENetPeerState::DisconnectLater;
} }
inline bool ENetPeer::IsSimulationEnabled() const
{
return m_isSimulationEnabled;
}
inline void ENetPeer::ChangeState(ENetPeerState state) inline void ENetPeer::ChangeState(ENetPeerState state)
{ {
if (state == ENetPeerState::Connected || state == ENetPeerState::DisconnectLater) if (state == ENetPeerState::Connected || state == ENetPeerState::DisconnectLater)

View File

@ -672,7 +672,7 @@ namespace Nz
if (m_isSimulationEnabled) if (m_isSimulationEnabled)
{ {
for (auto it = m_pendingPackets.begin(); it != m_pendingPackets.end(); ++it) for (auto it = m_pendingIncomingPackets.begin(); it != m_pendingIncomingPackets.end(); ++it)
{ {
if (m_serviceTime >= it->deliveryTime) if (m_serviceTime >= it->deliveryTime)
{ {
@ -682,7 +682,7 @@ namespace Nz
receivedLength = it->data.GetDataSize(); receivedLength = it->data.GetDataSize();
std::memcpy(m_packetData[0].data(), it->data.GetConstData() + NetPacket::HeaderSize, receivedLength); std::memcpy(m_packetData[0].data(), it->data.GetConstData() + NetPacket::HeaderSize, receivedLength);
m_pendingPackets.erase(it); m_pendingIncomingPackets.erase(it);
break; break;
} }
} }
@ -704,17 +704,17 @@ namespace Nz
UInt16 delay = m_packetDelayDistribution(s_randomGenerator); UInt16 delay = m_packetDelayDistribution(s_randomGenerator);
if (delay > 0) if (delay > 0)
{ {
PendingPacket pendingPacket; PendingIncomingPacket pendingPacket;
pendingPacket.deliveryTime = m_serviceTime + delay; pendingPacket.deliveryTime = m_serviceTime + delay;
pendingPacket.from = m_receivedAddress; pendingPacket.from = m_receivedAddress;
pendingPacket.data.Reset(0, m_packetData[0].data(), receivedLength); pendingPacket.data.Reset(0, m_packetData[0].data(), receivedLength);
auto it = std::upper_bound(m_pendingPackets.begin(), m_pendingPackets.end(), pendingPacket, [] (const PendingPacket& first, const PendingPacket& second) auto it = std::upper_bound(m_pendingIncomingPackets.begin(), m_pendingIncomingPackets.end(), pendingPacket, [] (const PendingIncomingPacket& first, const PendingIncomingPacket& second)
{ {
return first.deliveryTime < second.deliveryTime; return first.deliveryTime < second.deliveryTime;
}); });
m_pendingPackets.emplace(it, std::move(pendingPacket)); m_pendingIncomingPackets.emplace(it, std::move(pendingPacket));
continue; continue;
} }
} }
@ -1020,17 +1020,72 @@ namespace Nz
currentPeer->m_lastSendTime = m_serviceTime; currentPeer->m_lastSendTime = m_serviceTime;
std::size_t sentLength; // Simulate network by adding delay to packet sending and losing some packets
bool sendNow = true;
if (!currentPeer->IsSimulationEnabled())
{
sendNow = false;
if (!currentPeer->m_packetLossProbability(s_randomGenerator))
{
Nz::UInt16 delay = currentPeer->m_packetDelayDistribution(s_randomGenerator);
if (delay == 0)
sendNow = true;
else
{
PendingOutgoingPacket outgoingPacket;
outgoingPacket.deliveryTime = m_serviceTime + delay;
outgoingPacket.to = currentPeer->GetAddress();
// Accumulate every temporary buffer into a datagram
for (std::size_t i = 0; i < m_bufferCount; ++i)
{
NetBuffer& buffer = m_buffers[i];
outgoingPacket.data.Write(buffer.data, buffer.dataLength);
}
m_totalSentData += outgoingPacket.data.GetDataSize();
// Add it to the right place
auto it = std::upper_bound(m_pendingOutgoingPackets.begin(), m_pendingOutgoingPackets.end(), outgoingPacket, [](const PendingOutgoingPacket& first, const PendingOutgoingPacket& second)
{
return first.deliveryTime < second.deliveryTime;
});
m_pendingOutgoingPackets.emplace(it, std::move(outgoingPacket));
}
}
}
if (sendNow)
{
std::size_t sentLength = 0;
if (!m_socket.SendMultiple(currentPeer->GetAddress(), m_buffers.data(), m_bufferCount, &sentLength)) if (!m_socket.SendMultiple(currentPeer->GetAddress(), m_buffers.data(), m_bufferCount, &sentLength))
return -1; return -1;
currentPeer->RemoveSentUnreliableCommands();
m_totalSentData += sentLength; m_totalSentData += sentLength;
}
currentPeer->RemoveSentUnreliableCommands();
m_totalSentPackets++; m_totalSentPackets++;
} }
} }
if (!m_pendingOutgoingPackets.empty())
{
auto it = m_pendingOutgoingPackets.begin();
for (; it != m_pendingOutgoingPackets.end(); ++it)
{
if (m_serviceTime < it->deliveryTime)
break;
if (!m_socket.Send(it->to, it->data.GetConstData() + NetPacket::HeaderSize, it->data.GetDataSize(), nullptr))
return -1;
}
m_pendingOutgoingPackets.erase(m_pendingOutgoingPackets.begin(), it);
}
return 0; return 0;
} }

View File

@ -177,6 +177,20 @@ namespace Nz
return Send(channelId, m_host->AllocatePacket(flags, std::move(packet))); return Send(channelId, m_host->AllocatePacket(flags, std::move(packet)));
} }
void ENetPeer::SimulateNetwork(double packetLossProbability, UInt16 minDelay, UInt16 maxDelay)
{
NazaraAssert(maxDelay >= minDelay, "Maximum delay cannot be greater than minimum delay");
if (packetLossProbability <= 0.0 && minDelay == 0 && maxDelay == 0)
m_isSimulationEnabled = false;
else
{
m_isSimulationEnabled = true;
m_packetDelayDistribution = std::uniform_int_distribution<UInt16>(minDelay, maxDelay);
m_packetLossProbability = std::bernoulli_distribution(packetLossProbability);
}
}
bool ENetPeer::Send(UInt8 channelId, ENetPacketRef packetRef) bool ENetPeer::Send(UInt8 channelId, ENetPacketRef packetRef)
{ {
if (m_state != ENetPeerState::Connected || channelId >= m_channels.size() || packetRef->data.GetDataSize() > m_host->m_maximumPacketSize) if (m_state != ENetPeerState::Connected || channelId >= m_channels.size() || packetRef->data.GetDataSize() > m_host->m_maximumPacketSize)