using System; using System.Threading; using System.Collections.Generic; namespace Lidgren.Network { internal class ReceivedFragmentGroup { //public float LastReceived; public byte[] Data; public NetBitVector ReceivedChunks; } public partial class NetPeer { private int m_lastUsedFragmentGroup; private Dictionary> m_receivedFragmentGroups; // on user thread private NetSendResult SendFragmentedMessage(NetOutgoingMessage msg, IList recipients, NetDeliveryMethod method, int sequenceChannel) { // Note: this group id is PER SENDING/NetPeer; ie. same id is sent to all recipients; // this should be ok however; as long as recipients differentiate between same id but different sender int group = Interlocked.Increment(ref m_lastUsedFragmentGroup); if (group >= NetConstants.MaxFragmentationGroups) { // @TODO: not thread safe; but in practice probably not an issue m_lastUsedFragmentGroup = 1; group = 1; } msg.m_fragmentGroup = group; // do not send msg; but set fragmentgroup in case user tries to recycle it immediately // create fragmentation specifics int totalBytes = msg.LengthBytes; // determine minimum mtu for all recipients int mtu = GetMTU(recipients); int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu); int numChunks = totalBytes / bytesPerChunk; if (numChunks * bytesPerChunk < totalBytes) numChunks++; NetSendResult retval = NetSendResult.Sent; int bitsPerChunk = bytesPerChunk * 8; int bitsLeft = msg.LengthBits; for (int i = 0; i < numChunks; i++) { NetOutgoingMessage chunk = CreateMessage(0); chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft); chunk.m_data = msg.m_data; chunk.m_fragmentGroup = group; chunk.m_fragmentGroupTotalBits = totalBytes * 8; chunk.m_fragmentChunkByteSize = bytesPerChunk; chunk.m_fragmentChunkNumber = i; NetException.Assert(chunk.m_bitLength != 0); NetException.Assert(chunk.GetEncodedSize() < mtu); Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count); foreach (NetConnection recipient in recipients) { var res = recipient.EnqueueMessage(chunk, method, sequenceChannel); if ((int)res > (int)retval) retval = res; // return "worst" result } bitsLeft -= bitsPerChunk; } return retval; } private void HandleReleasedFragment(NetIncomingMessage im) { VerifyNetworkThread(); // // read fragmentation header and combine fragments // int group; int totalBits; int chunkByteSize; int chunkNumber; int ptr = NetFragmentationHelper.ReadHeader( im.m_data, 0, out group, out totalBits, out chunkByteSize, out chunkNumber ); NetException.Assert(im.LengthBytes > ptr); NetException.Assert(group > 0); NetException.Assert(totalBits > 0); NetException.Assert(chunkByteSize > 0); int totalBytes = NetUtility.BytesToHoldBits((int)totalBits); int totalNumChunks = totalBytes / chunkByteSize; if (totalNumChunks * chunkByteSize < totalBytes) totalNumChunks++; NetException.Assert(chunkNumber < totalNumChunks); if (chunkNumber >= totalNumChunks) { LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")"); return; } Dictionary groups; if (!m_receivedFragmentGroups.TryGetValue(im.SenderConnection, out groups)) { groups = new Dictionary(); m_receivedFragmentGroups[im.SenderConnection] = groups; } ReceivedFragmentGroup info; if (!groups.TryGetValue(group, out info)) { info = new ReceivedFragmentGroup(); info.Data = new byte[totalBytes]; info.ReceivedChunks = new NetBitVector(totalNumChunks); groups[group] = info; } info.ReceivedChunks[chunkNumber] = true; //info.LastReceived = (float)NetTime.Now; // copy to data int offset = (chunkNumber * chunkByteSize); Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr); int cnt = info.ReceivedChunks.Count(); //LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")"); LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)"); if (info.ReceivedChunks.Count() == totalNumChunks) { // Done! Transform this incoming message im.m_data = info.Data; im.m_bitLength = (int)totalBits; im.m_isFragment = false; LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)"); groups.Remove(group); ReleaseMessage(im); } else { // data has been copied; recycle this incoming message Recycle(im); } return; } } }