Network/ENet: Add peer-side lag simulation
This commit is contained in:
parent
cc57fed42e
commit
b0d0a63fca
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Nz
|
||||||
{
|
{
|
||||||
ENetHost_BandwidthThrottleInterval = 1000,
|
ENetHost_BandwidthThrottleInterval = 1000,
|
||||||
ENetHost_DefaultMaximumPacketSize = 32 * 1024 * 1024,
|
ENetHost_DefaultMaximumPacketSize = 32 * 1024 * 1024,
|
||||||
ENetHost_DefaultMaximumWaitingData = 32 * 1024 * 1024,
|
ENetHost_DefaultMaximumWaitingData = 32 * 1024 * 1024,
|
||||||
ENetHost_DefaultMTU = 1400,
|
ENetHost_DefaultMTU = 1400,
|
||||||
ENetHost_ReceiveBufferSize = 256 * 1024,
|
ENetHost_ReceiveBufferSize = 256 * 1024,
|
||||||
ENetHost_SendBufferSize = 256 * 1024,
|
ENetHost_SendBufferSize = 256 * 1024,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
if (!m_socket.SendMultiple(currentPeer->GetAddress(), m_buffers.data(), m_bufferCount, &sentLength))
|
bool sendNow = true;
|
||||||
return -1;
|
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))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
m_totalSentData += sentLength;
|
||||||
|
}
|
||||||
|
|
||||||
currentPeer->RemoveSentUnreliableCommands();
|
currentPeer->RemoveSentUnreliableCommands();
|
||||||
|
|
||||||
m_totalSentData += sentLength;
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue