diff --git a/include/Nazara/Network/ENetHost.hpp b/include/Nazara/Network/ENetHost.hpp index b1304ab81..edb9155ab 100644 --- a/include/Nazara/Network/ENetHost.hpp +++ b/include/Nazara/Network/ENetHost.hpp @@ -100,13 +100,20 @@ namespace Nz static bool Initialize(); static void Uninitialize(); - struct PendingPacket + struct PendingIncomingPacket { IpAddress from; NetPacket data; UInt32 deliveryTime; }; + struct PendingOutgoingPacket + { + IpAddress to; + NetPacket data; + UInt32 deliveryTime; + }; + std::array m_commands; std::array m_buffers; std::array m_packetData[2]; @@ -123,7 +130,8 @@ namespace Nz std::size_t m_receivedDataLength; std::uniform_int_distribution m_packetDelayDistribution; std::vector m_peers; - std::vector m_pendingPackets; + std::vector m_pendingIncomingPackets; + std::vector m_pendingOutgoingPackets; UInt8* m_receivedData; Bitset m_dispatchQueue; MemoryPool m_packetPool; diff --git a/include/Nazara/Network/ENetPeer.hpp b/include/Nazara/Network/ENetPeer.hpp index 19144c2f2..e3538e6fb 100644 --- a/include/Nazara/Network/ENetPeer.hpp +++ b/include/Nazara/Network/ENetPeer.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace Nz @@ -59,6 +60,7 @@ namespace Nz inline bool HasPendingCommands(); inline bool IsConnected() const; + inline bool IsSimulationEnabled() const; void Ping(); @@ -68,6 +70,8 @@ namespace Nz bool Send(UInt8 channelId, ENetPacketRef packetRef); bool Send(UInt8 channelId, ENetPacketFlags flags, NetPacket&& packet); + void SimulateNetwork(double packetLossProbability, UInt16 minDelay, UInt16 maxDelay); + void ThrottleConfigure(UInt32 interval, UInt32 acceleration, UInt32 deceleration); ENetPeer& operator=(const ENetPeer&) = delete; @@ -177,12 +181,14 @@ namespace Nz ENetHost* m_host; IpAddress m_address; /**< Internet address of the peer */ std::array m_unsequencedWindow; + std::bernoulli_distribution m_packetLossProbability; std::list m_dispatchedCommands; std::list m_outgoingReliableCommands; std::list m_outgoingUnreliableCommands; std::list m_sentReliableCommands; std::list m_sentUnreliableCommands; std::size_t m_totalWaitingData; + std::uniform_int_distribution m_packetDelayDistribution; std::vector m_acknowledgements; std::vector m_channels; ENetPeerState m_state; @@ -232,6 +238,7 @@ namespace Nz UInt32 m_windowSize; UInt64 m_totalPacketLost; UInt64 m_totalPacketSent; + bool m_isSimulationEnabled; }; } diff --git a/include/Nazara/Network/ENetPeer.inl b/include/Nazara/Network/ENetPeer.inl index e9c15fe35..bcec4a8c1 100644 --- a/include/Nazara/Network/ENetPeer.inl +++ b/include/Nazara/Network/ENetPeer.inl @@ -12,7 +12,8 @@ namespace Nz m_host(host), m_incomingSessionID(0xFF), m_outgoingSessionID(0xFF), - m_incomingPeerID(peerId) + m_incomingPeerID(peerId), + m_isSimulationEnabled(false) { Reset(); } @@ -62,6 +63,11 @@ namespace Nz return m_state == ENetPeerState::Connected || m_state == ENetPeerState::DisconnectLater; } + inline bool ENetPeer::IsSimulationEnabled() const + { + return m_isSimulationEnabled; + } + inline void ENetPeer::ChangeState(ENetPeerState state) { if (state == ENetPeerState::Connected || state == ENetPeerState::DisconnectLater) diff --git a/include/Nazara/Network/ENetProtocol.hpp b/include/Nazara/Network/ENetProtocol.hpp index e3df5e092..7cd613ab0 100644 --- a/include/Nazara/Network/ENetProtocol.hpp +++ b/include/Nazara/Network/ENetProtocol.hpp @@ -28,7 +28,7 @@ namespace Nz { ENetHost_BandwidthThrottleInterval = 1000, ENetHost_DefaultMaximumPacketSize = 32 * 1024 * 1024, - ENetHost_DefaultMaximumWaitingData = 32 * 1024 * 1024, + ENetHost_DefaultMaximumWaitingData = 32 * 1024 * 1024, ENetHost_DefaultMTU = 1400, ENetHost_ReceiveBufferSize = 256 * 1024, ENetHost_SendBufferSize = 256 * 1024, diff --git a/src/Nazara/Network/ENetHost.cpp b/src/Nazara/Network/ENetHost.cpp index 76c81eb38..84083b24d 100644 --- a/src/Nazara/Network/ENetHost.cpp +++ b/src/Nazara/Network/ENetHost.cpp @@ -672,7 +672,7 @@ namespace Nz 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) { @@ -682,7 +682,7 @@ namespace Nz receivedLength = it->data.GetDataSize(); std::memcpy(m_packetData[0].data(), it->data.GetConstData() + NetPacket::HeaderSize, receivedLength); - m_pendingPackets.erase(it); + m_pendingIncomingPackets.erase(it); break; } } @@ -704,17 +704,17 @@ namespace Nz UInt16 delay = m_packetDelayDistribution(s_randomGenerator); if (delay > 0) { - PendingPacket pendingPacket; + PendingIncomingPacket pendingPacket; pendingPacket.deliveryTime = m_serviceTime + delay; pendingPacket.from = m_receivedAddress; 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; }); - m_pendingPackets.emplace(it, std::move(pendingPacket)); + m_pendingIncomingPackets.emplace(it, std::move(pendingPacket)); continue; } } @@ -1020,17 +1020,72 @@ namespace Nz currentPeer->m_lastSendTime = m_serviceTime; - std::size_t sentLength; - if (!m_socket.SendMultiple(currentPeer->GetAddress(), m_buffers.data(), m_bufferCount, &sentLength)) - return -1; + // 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)) + return -1; + + m_totalSentData += sentLength; + } currentPeer->RemoveSentUnreliableCommands(); - - m_totalSentData += sentLength; 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; } diff --git a/src/Nazara/Network/ENetPeer.cpp b/src/Nazara/Network/ENetPeer.cpp index e7a39e3e5..1d28cae42 100644 --- a/src/Nazara/Network/ENetPeer.cpp +++ b/src/Nazara/Network/ENetPeer.cpp @@ -177,6 +177,20 @@ namespace Nz 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(minDelay, maxDelay); + m_packetLossProbability = std::bernoulli_distribution(packetLossProbability); + } + } + bool ENetPeer::Send(UInt8 channelId, ENetPacketRef packetRef) { if (m_state != ENetPeerState::Connected || channelId >= m_channels.size() || packetRef->data.GetDataSize() > m_host->m_maximumPacketSize)