diff --git a/Lidgren.Network/NetPeer.MessagePools.cs b/Lidgren.Network/NetPeer.MessagePools.cs index f8f0c4c..e2caf8c 100644 --- a/Lidgren.Network/NetPeer.MessagePools.cs +++ b/Lidgren.Network/NetPeer.MessagePools.cs @@ -6,14 +6,18 @@ namespace Lidgren.Network { public partial class NetPeer { - private List m_storagePool; // sorted smallest to largest + internal List m_storagePool; private NetQueue m_outgoingMessagesPool; private NetQueue m_incomingMessagesPool; internal int m_storagePoolBytes; + internal int m_storageSlotsUsedCount; + private int m_maxCacheCount; private void InitializePools() { + m_storageSlotsUsedCount = 0; + if (m_configuration.UseMessageRecycling) { m_storagePool = new List(16); @@ -26,6 +30,8 @@ namespace Lidgren.Network m_outgoingMessagesPool = null; m_incomingMessagesPool = null; } + + m_maxCacheCount = m_configuration.RecycledCacheMaxCount; } internal byte[] GetStorage(int minimumCapacityInBytes) @@ -41,6 +47,7 @@ namespace Lidgren.Network if (retval != null && retval.Length >= minimumCapacityInBytes) { m_storagePool[i] = null; + m_storageSlotsUsedCount--; m_storagePoolBytes -= retval.Length; return retval; } @@ -57,17 +64,34 @@ namespace Lidgren.Network lock (m_storagePool) { - m_storagePoolBytes += storage.Length; int cnt = m_storagePool.Count; for (int i = 0; i < cnt; i++) { if (m_storagePool[i] == null) { + m_storageSlotsUsedCount++; + m_storagePoolBytes += storage.Length; m_storagePool[i] = storage; return; } } - m_storagePool.Add(storage); + + if (m_storagePool.Count >= m_maxCacheCount) + { + // pool is full; replace randomly chosen entry to keep size distribution + var idx = NetRandom.Instance.Next(m_storagePool.Count); + + m_storagePoolBytes -= m_storagePool[idx].Length; + m_storagePoolBytes += storage.Length; + + m_storagePool[idx] = storage; // replace + } + else + { + m_storageSlotsUsedCount++; + m_storagePoolBytes += storage.Length; + m_storagePool.Add(storage); + } } } @@ -141,7 +165,9 @@ namespace Lidgren.Network msg.m_data = null; Recycle(storage); msg.Reset(); - m_incomingMessagesPool.Enqueue(msg); + + if (m_incomingMessagesPool.Count < m_maxCacheCount) + m_incomingMessagesPool.Enqueue(msg); } /// @@ -151,34 +177,8 @@ namespace Lidgren.Network { if (m_incomingMessagesPool == null) return; - - // first recycle the storage of each message - if (m_storagePool != null) - { - lock (m_storagePool) - { - foreach (var msg in toRecycle) - { - var storage = msg.m_data; - msg.m_data = null; - m_storagePoolBytes += storage.Length; - int cnt = m_storagePool.Count; - for (int i = 0; i < cnt; i++) - { - if (m_storagePool[i] == null) - { - m_storagePool[i] = storage; - return; - } - } - msg.Reset(); - m_storagePool.Add(storage); - } - } - } - - // then recycle the message objects - m_incomingMessagesPool.Enqueue(toRecycle); + foreach (var im in toRecycle) + Recycle(im); } internal void Recycle(NetOutgoingMessage msg) @@ -187,17 +187,18 @@ namespace Lidgren.Network return; NetException.Assert(m_outgoingMessagesPool.Contains(msg) == false, "Recyling already recycled message! Thread race?"); - + byte[] storage = msg.m_data; msg.m_data = null; - + // message fragments cannot be recycled // TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them if (msg.m_fragmentGroup == 0) Recycle(storage); - + msg.Reset(); - m_outgoingMessagesPool.Enqueue(msg); + if (m_outgoingMessagesPool.Count < m_maxCacheCount) + m_outgoingMessagesPool.Enqueue(msg); } /// diff --git a/Lidgren.Network/NetPeerConfiguration.cs b/Lidgren.Network/NetPeerConfiguration.cs index c5ed3e5..7989d4c 100644 --- a/Lidgren.Network/NetPeerConfiguration.cs +++ b/Lidgren.Network/NetPeerConfiguration.cs @@ -53,6 +53,7 @@ namespace Lidgren.Network internal int m_defaultOutgoingMessageCapacity; internal float m_pingInterval; internal bool m_useMessageRecycling; + internal int m_recycledCacheMaxCount; internal float m_connectionTimeout; internal bool m_enableUPnP; internal bool m_autoFlushSendQueue; @@ -107,6 +108,7 @@ namespace Lidgren.Network m_pingInterval = 4.0f; m_connectionTimeout = 25.0f; m_useMessageRecycling = true; + m_recycledCacheMaxCount = 64; m_resendHandshakeInterval = 3.0f; m_maximumHandshakeAttempts = 5; m_autoFlushSendQueue = true; @@ -258,6 +260,20 @@ namespace Lidgren.Network } } + /// + /// Gets or sets the maximum number of incoming/outgoing messages to keep in the recycle cache. + /// + public int RecycledCacheMaxCount + { + get { return m_recycledCacheMaxCount; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_recycledCacheMaxCount = value; + } + } + /// /// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong /// diff --git a/Lidgren.Network/NetPeerStatistics.cs b/Lidgren.Network/NetPeerStatistics.cs index de6b81d..67d217e 100644 --- a/Lidgren.Network/NetPeerStatistics.cs +++ b/Lidgren.Network/NetPeerStatistics.cs @@ -157,7 +157,8 @@ namespace Lidgren.Network bdr.AppendLine("Received (n/a) bytes in (n/a) messages in (n/a) packets"); #endif bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes"); - bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes"); + if (m_peer.m_storagePool != null) + bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes (" + m_peer.m_storageSlotsUsedCount + " entries)"); return bdr.ToString(); } } diff --git a/Samples/SamplesCommon/NetPeerSettingsWindow.cs b/Samples/SamplesCommon/NetPeerSettingsWindow.cs index 2a58bff..f076f9f 100644 --- a/Samples/SamplesCommon/NetPeerSettingsWindow.cs +++ b/Samples/SamplesCommon/NetPeerSettingsWindow.cs @@ -57,8 +57,6 @@ namespace SamplesCommon var minLat = (pc.SimulatedMinimumLatency * 1000.0f).ToString(); var maxLat = ((pc.SimulatedMinimumLatency + pc.SimulatedRandomLatency) * 1000.0f).ToString(); #else - var loss = 0; - var dupes = 0; var minLat = ""; var maxLat = ""; #endif