/// <summary> /// Broadcasts a single chunk and stores it in the frame buffer. /// </summary> /// <param name="chunk">The chunk to send</param> private void RtpSend(Chunk chunk, MessagePriority priority) { using(Synchronizer.Lock(this)) { using(MemoryStream stream = new MemoryStream((int) (this.m_Encoder.MaximumChunkSize * 2))) { // Store the sequence number of non-real-time chunks so they can be NACKed. // Use "++this.m_FrameSequence" so that non-real-time chunks always have a // FrameSequence that is greater than 0. if (priority != MessagePriority.RealTime) chunk.FrameSequence = ++this.m_FrameSequence; // If the chunk is not real time (or it is being resent a second time), // store the chunk in the buffer so a NACK request can resend it. if (chunk.FrameSequence > ulong.MinValue) this.m_FrameBuffer.Insert(chunk); // Buffering the chunk probably evicted other chunks from the buffer, // so get the oldest frame in the buffer so receivers don't waste time // sending NACKs for chunks that are no longer available. chunk.OldestRecoverableFrame = this.m_FrameBuffer.OldestRecoverableFrame; chunk.OldestRecoverableMessage = this.m_FrameBuffer.OldestRecoverableMessage; // Serialize the chunk (which itself contains a serialized message). // TODO: see whether the wire protocol can be made more efficient. stream.Position = 0; this.m_Encoder.EncodeChunk(stream, chunk); // Prepare the frame to be sent over RTP. BufferChunk buffer = new BufferChunk(stream.GetBuffer(), 0, (int) stream.Length); // Send it (or drop some percentage of packets if the debug option is enabled). if(DEBUG_DROP_RANDOM_FRAMES <= 0 || new Random().Next(100) >= DEBUG_DROP_RANDOM_FRAMES) { Debug.WriteLine(string.Format("Sending frame #{0} ({1} bytes, message #{2}, chunk #{3} of {4}, depending on #{5}, priority {6})", chunk.FrameSequence, chunk.Data.Length, chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.MessageDependency, priority), this.GetType().ToString()); try { this.m_RtpSender.Send( buffer ); } catch( Exception ) { this.Reset(); } } else { Debug.WriteLine(string.Format("DEBUG: Dropping frame #{0} ({1} bytes, message #{2}, chunk #{3} of {4}, depending on #{5}, priority {6})", chunk.FrameSequence, chunk.Data.Length, chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.MessageDependency, priority), this.GetType().ToString()); } } } }
/// <summary> /// Writes a chunk to a data stream. /// </summary> public void EncodeChunk(Stream stream, Chunk chunk) { // FIXME: More efficient encoding of chunks might be possible here. // Custom encoding would require a Decode method to be used by RTPMessageReceiver. this.m_Formatter.Serialize(stream, chunk); }
/// <summary> /// Serializes the given message and constructs a number of /// chunks containing the serialized data. /// </summary> /// <param name="message">The message to serialize.</param> /// <param name="messageSequence"> /// A reference to a counter which will be incremented. /// This becomes the value of each chunk's /// <see cref="Chunk.MessageSequence"/> field. /// </param> /// <returns> /// A non-empty array containing the created chunks. /// </returns> public Chunk[] MakeChunks(Message message, ref ulong messageSequence) { ++messageSequence; ulong currentChunkSequence = 0; ulong maxSize = this.m_MaximumChunkSize; using(MemoryStream stream = new MemoryStream()) { // Serialize the message. this.m_Formatter.Serialize(stream, message); // Calculate the number of chunks needed to span the message and allocate an array to hold them. ulong length = ((ulong) stream.Length); ulong count = (length / maxSize) + (length % maxSize == 0ul ? 0ul : 1ul); Chunk[] chunks = new Chunk[count]; stream.Position = 0; int index = 0; for(ulong amountLeft; (amountLeft = length - ((ulong) stream.Position)) > 0; index++) { // Read the deserialized data into a small array. byte[] chunk = new byte[amountLeft < maxSize ? amountLeft : maxSize]; int read = stream.Read(chunk, 0, chunk.Length); if(read != chunk.Length) throw new ApplicationException("Couldn't read enough data from MemoryStream."); // Create a chunk, updating the sequence numbers. chunks[index] = new Chunk(chunk, messageSequence, currentChunkSequence++, count); } return chunks; } }
/// <summary> /// Updates <see cref="m_Buffer"/> with the specified chunk at the specified index, /// and also keeps the <see cref="OldestMessageSequence"/> value up-to-date. /// </summary> /// <param name="index">The index at which to place the chunk.</param> /// <param name="chunk"> /// The chunk to insert, or <c>null</c> if the chunk is being removed from the buffer. /// </param> private void Set(ulong index, Chunk chunk) { ulong count; ulong message; // First decrement the count for the OLD chunk's message sequence number. Chunk old = this.m_Buffer[index]; if (old != null) { message = old.MessageSequence; if (message > ulong.MinValue) if (this.m_MessageSequences.TryGetValue(message, out count) && count > 0) if (count <= 1) this.m_MessageSequences.Remove(message); else this.m_MessageSequences[message] = count - 1; else throw new ApplicationException("Unmatched insertions and removals into the FrameBuffer."); } // Now insert the chunk in the buffer. this.m_Buffer[index] = chunk; // Finally increment the count for the NEW chunk's message sequence number. if (chunk != null) { message = chunk.MessageSequence; if (message > ulong.MinValue) if (this.m_MessageSequences.TryGetValue(message, out count)) this.m_MessageSequences[message] = count + 1; else this.m_MessageSequences[message] = 1; } }
/// <summary> /// If connected, send a message, otherwise do nothing. /// </summary> /// <param name="chunk"></param> /// <param name="group"></param> public void Send(Chunk chunk, Group group, MessageTags tags) { AsyncCallback ac = new AsyncCallback(SendCallback); using (MemoryStream stream = new MemoryStream((int)(this.m_Encoder.MaximumChunkSize * 2))) { stream.Position = 0; this.m_Encoder.EncodeChunk(stream, chunk); Trace.WriteLine(string.Format("TCPClient sending message #{0}, chunk #{1} of {2}, {3} bytes.", chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.Data.Length), this.GetType().ToString()); byte[] buffer = stream.GetBuffer(); int length = (int)stream.Length; if ((m_Socket.Connected) && (m_Connected)) while (true) { try { m_Socket.BeginSend(buffer, 0, length, SocketFlags.None, ac, new SendState(m_Socket, length)); //BeginSend since it won't block. break; } catch (SocketException se) { if (se.ErrorCode == 10055) { Trace.WriteLine("Client Send queue is full. Sleeping 50 mS", this.GetType().ToString()); Thread.Sleep(50); } else { Trace.WriteLine("SendThread socket exception: " + se.ToString() + " Error code: " + se.ErrorCode.ToString(), this.GetType().ToString()); break; } } catch (ObjectDisposedException ode) { Trace.WriteLine("SendThread ObjectDisposedException: " + ode.Message, this.GetType().ToString()); break; } catch (Exception e) { Trace.WriteLine("SendThread Exception: " + e.ToString(), this.GetType().ToString()); break; } } else { Trace.WriteLine("SendThread found a socket disconnected. Send request ignored.", this.GetType().ToString()); } } }
/// <summary> /// Broadcasts a single chunk and stores it in the frame buffer. /// </summary> /// <param name="chunk">The chunk to send</param> private void CapabilitySend(Chunk chunk, MessagePriority priority) { using(Synchronizer.Lock(this)) { // Store the sequence number of non-real-time chunks so they can be NACKed. // Use "++this.m_FrameSequence" so that non-real-time chunks always have a // FrameSequence that is greater than 0. if (priority != MessagePriority.RealTime) chunk.FrameSequence = ++this.m_FrameSequence; // If the chunk is not real time (or it is being resent a second time), // store the chunk in the buffer so a NACK request can resend it. if (chunk.FrameSequence > ulong.MinValue) this.m_FrameBuffer.Insert(chunk); // Buffering the chunk probably evicted other chunks from the buffer, // so get the oldest frame in the buffer so receivers don't waste time // sending NACKs for chunks that are no longer available. chunk.OldestRecoverableFrame = this.m_FrameBuffer.OldestRecoverableFrame; chunk.OldestRecoverableMessage = this.m_FrameBuffer.OldestRecoverableMessage; Debug.WriteLine(string.Format("Sending frame #{0} ({1} bytes, message #{2}, chunk #{3} of {4}, depending on #{5}, priority {6})", chunk.FrameSequence, chunk.Data.Length, chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.MessageDependency, priority), this.GetType().ToString()); SendObject(new CapabilityMessageWrapper(chunk, m_LocalId, Guid.Empty, m_Capability.IsSender)); } }
/// <summary> /// Finds and removes the chunk with the given /// <see cref="Chunk.FrameSequence">frame sequence number</see> from the buffer. /// <remarks> /// Note that this does NOT update <see cref="OldestRecoverableFrame"/> /// or <see cref="OldestRecoverableMessage"/>. It is expected that the /// chunk will be reinserted into the buffer in the future. /// </remarks> /// </summary> /// <param name="frameSequence"> /// The <see cref="Chunk.FrameSequence">frame sequence number</see> to extract /// from the buffer. /// </param> /// <param name="chunk">The chunk extracted from the buffer, if available.</param> /// <returns> /// <c>true</c> if the given <paramref name="frameSequence"/> was in the range /// <see cref="OldestRecoverableFrame"/> to <see cref="NewestRecoverableFrame"/>; /// <c>false</c> otherwise. (A <c>true</c> return value does not imply that /// <paramref name="chunk"/> was set to non-null, since the chunk with the given /// <paramref name="frameSequence"/> might have been previously been removed from /// the buffer by <see cref="Take"/>, or might never have been inserted in the /// first place.) /// </returns> public bool Take(ulong frameSequence, out Chunk chunk) { if (this.m_Oldest <= frameSequence && frameSequence <= this.m_Newest) { ulong index = frameSequence % this.m_BufferSize; // FIXME: This shouldn't update OldestMessageSequence. // But if we don't, then Insert() will cause the count to get unbalanced. chunk = this.m_Buffer[index]; this.Set(index, null); Debug.Assert(chunk == null || chunk.FrameSequence == frameSequence, "FrameBuffer table entry has mismatched frame sequence number."); return true; } else { chunk = null; return false; } }
/// <summary> /// Attempts to assemble a chunk from a chunked messages, /// and sends a NACK when dropped chunks are detected. /// </summary> /// <param name="chunk">The received chunk to process</param> /// <returns>If the chunk was the last remaining chunk needed to complete a message, /// returns the deserialized message; otherwise, <code>null</code></returns> public IEnumerable<object> Add(Chunk chunk) { if (chunk.FrameSequence > 0) { // Check if we have seen this packet before. If so, do nothing. if (DisjointSet.Contains(ref this.m_ReceivedFrameSequences, chunk.FrameSequence)) yield break; DisjointSet.Add(ref this.m_ReceivedFrameSequences, chunk.FrameSequence); // Save space in the ReceivedSequenceNumbers queue by adding all chunks // that we can't expect to receive anymore. if (chunk.OldestRecoverableFrame > ulong.MinValue) DisjointSet.Add(ref this.m_ReceivedFrameSequences, new Range(ulong.MinValue, chunk.OldestRecoverableFrame - 1)); // Check the frame sequence number to see if any frames were skipped. // If so, then it is likely that the frame has been dropped, so it is // necessary to send a NACK. if (chunk.FrameSequence > this.m_GreatestFrameSequence + 1ul) { //Debug.WriteLine(string.Format("Frames #{0}-{1} were dropped ({2} total)! Requesting replacements (oldest recoverable is #{3})...", // this.m_GreatestFrameSequence + 1ul, chunk.FrameSequence - 1ul, chunk.FrameSequence - this.m_GreatestFrameSequence - 1, // chunk.OldestRecoverableFrame), this.GetType().ToString()); if (this.Nack != null) { // Send a NACK which requests rebroadcast of all the skipped frames. Range range = new Range( Math.Max(this.m_GreatestFrameSequence + 1ul, chunk.OldestRecoverableFrame), chunk.FrameSequence - 1ul); this.Nack(range); } } // Otherwise, don't do anything special. These two branches are for debugging. else if (chunk.FrameSequence == this.m_GreatestFrameSequence) { // Duplicate chunk received, or else the first chunk ever received. } else if (chunk.FrameSequence < this.m_GreatestFrameSequence && chunk.FrameSequence > ulong.MinValue) { Debug.WriteLine(string.Format("%%% Frame #{0} (message #{1}, chunk #{2} of {3}, {4} bytes) received out of order (off by {5})", chunk.FrameSequence, chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.Data.Length, this.m_GreatestFrameSequence - chunk.FrameSequence), this.GetType().ToString()); } // Assuming the chunk has not been duplicated or received out of order, // update our variable containing the highest sequence number seen // so far so we know which future frames have been dropped. if (chunk.FrameSequence > this.m_GreatestFrameSequence) this.m_GreatestFrameSequence = chunk.FrameSequence; } // If the message depends on a message that has not yet been received, // add it to the queue of chunks waiting for that message. // Then, STOP PROCESSING the message. // Assemble(chunk) will get called by FlushWaitingChunks after // the dependencies are satisfied. if (chunk.MessageDependency > ulong.MinValue && !DisjointSet.Contains(ref this.m_ReceivedMessages, chunk.MessageDependency)) { List<Chunk> waiters; if (!this.m_ChunksWaitingForDependencies.TryGetValue(chunk.MessageDependency, out waiters)) this.m_ChunksWaitingForDependencies[chunk.MessageDependency] = waiters = new List<Chunk>(); Debug.WriteLine(string.Format("@@@ Unsatisfied dependency: message #{0} (chunk #{1} of {2}) depends on #{3}; {4} chunks are already waiting.", chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.MessageDependency, waiters.Count)); waiters.Add(chunk); yield break; } // Flush any dependencies of messages we can no longer expect to recover. // Do this by finding the difference between the range of all unrecoverable // message sequence numbers and the sequence numbers that have been received. // FlushWaitingChunks is called on each of the unsatisfiable sequence numbers. if (chunk.OldestRecoverableMessage > ulong.MinValue) { Range unrecoverable = new Range(ulong.MinValue, chunk.OldestRecoverableMessage - 1); DisjointSet unsatisfiable = unrecoverable; DisjointSet.Remove(ref unsatisfiable, this.m_ReceivedMessages); if(unsatisfiable != null) foreach (ulong unsatisfied in unsatisfiable) this.FlushWaitingChunks(unsatisfied); DisjointSet.Add(ref this.m_ReceivedMessages, unrecoverable); } // If the dependency is already satisfied (or not specified), // attempt to assemble the chunk into a completed message. foreach (object message in this.Assemble(chunk)) yield return message; }
public ChunksAndPriority(Chunk[] chunks, MessageTags tags) { this.Chunks = chunks; this.Tags = tags; }
/// <summary> /// Adds a chunk to the message. /// The chunk must be part of the message; /// that is, <see cref="IsInRange">IsInRange(<paramref name="chunk"/>)</see> /// must return <c>true</c>. /// </summary> /// <param name="chunk"> /// The chunk to add to the message. /// </param> public void Add(Chunk chunk) { ulong index = chunk.ChunkSequenceInMessage; ulong chunks = ((ulong)this.m_Data.LongLength); if (index < 0 || index >= chunks) throw new ArgumentException("Chunk is not part of the message being assembled by this MessageAssembler.", "chunk"); // If the chunk is not a duplicate, insert its contents into the data array. // (The chunked message cannot be deserialized until all chunks have been received.) if (this.m_Data[index] == null) { Debug.Assert(this.m_Remaining >= 1); // This chunk has not been received before. this.m_Data[index] = chunk.Data; this.m_Remaining--; Debug.WriteLine(string.Format("Received message #{0}, chunk #{0} of {1} ({2} bytes); {3} chunks remaining.", chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.Data.LongLength, this.m_Remaining), this.GetType().ToString()); } }
/// <summary> /// Gets whether the given chunk is part of the message /// being assembled by this <see cref="MessageAssembler"/>. /// </summary> /// <param name="chunk"> /// The chunk to test. /// </param> /// <returns> /// Whether the chunk's sequence number is in range. /// </returns> public bool IsInRange(Chunk chunk) { return chunk.MessageSequence == this.m_MessageSequence; }
/// <summary> /// Attempts to assemble the chunk into a completed message. /// If this is the last chunk of a message, the completed message /// is returned, and any unsatisfied dependencies that are now /// satisfied by the completed message are recursively assembled. /// All such assembled message are also returned via the enumeration. /// </summary> /// <remarks> /// This method is called by <see cref="Add"/> and <see cref="FlushWaitingChunks"/>, /// after all chunk dependencies are satisfied. /// </remarks> /// <param name="chunk">The chunk to assemble.</param> /// <returns> /// The enumeration of decoded messages. /// The enumeration may be empty or may /// have arbitrarily many members if dependencies were /// (recursively) satisfied by this chunk. /// </returns> protected IEnumerable<object> Assemble(Chunk chunk) { // Process single-chunk messages immediately. if (chunk.NumberOfChunksInMessage <= 1) { // Don't create a MessageAssembler for singleton chunks. // Instead, just return the message immediately. using (MemoryStream ms = new MemoryStream(chunk.Data)) { yield return this.m_Formatter.Deserialize(ms); // The message has been completed, so flush any chunks which were waiting for it. foreach (object dependent in this.FlushWaitingChunks(chunk.MessageSequence)) yield return dependent; yield break; } } // For multi-chunk messages, we first attempt to find an existing MessageAssembler // instance for the message to which the chunk belongs (based on the range of chunk // sequence numbers the message spans). MessageAssembler assembler = this.NewestMessageAssember, previous = null; object message; for (; ; ) { bool done, remove, complete; // If there does not exist any assembler for which IsInRange(chunk) returned true, // create one to hold the chunk. if (assembler == null) { Debug.WriteLine(string.Format("Creating a new MessageAssembler to manage multipart message (message #{0}, chunks #{1}-{2})", chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage), this.GetType().ToString()); assembler = new MessageAssembler(chunk.MessageSequence, chunk.NumberOfChunksInMessage, this.m_Formatter); // Insert the assembler as the first entry in our linked list, // since it is most likely to be used by subsequent chunks. assembler.NextOldestAssembler = this.NewestMessageAssember; this.NewestMessageAssember = assembler; } // See if the chunk belongs to the current assembler. if (assembler.MessageSequence == chunk.MessageSequence) { // If so, add the chunk to it, and we can stop searching. assembler.Add(chunk); done = true; // If the message has been fully assembled, process it // and remove the no-longer-needed assembler. complete = assembler.IsComplete; if (complete) { message = assembler.DeserializeMessage(); remove = true; } else { message = null; remove = false; } } else if (assembler.MessageSequence < chunk.OldestRecoverableMessage) { // For each message assembler that is waiting for more chunks (and to which the current // chunk does not belong), make sure it will be possible to complete the message in // the future. If the sender reports that its OldestRecoverableFrame is greater than // the sequence number of any frame yet needed to complete the message, then no // NACK we send can ever satisfy our needs, so we discard the message completely // (removing the assembler from the linked list). Debug.WriteLine(string.Format("### Giving up on message #{0} (chunks #{0}-{1}): the oldest available chunk is {2}!", chunk.MessageSequence, chunk.ChunkSequenceInMessage + 1, chunk.NumberOfChunksInMessage, chunk.OldestRecoverableMessage), this.GetType().ToString()); remove = true; message = null; done = false; complete = false; } else { remove = false; message = null; done = false; complete = false; } // If the assembler is no longer useful, remove it from the linked list. // (There are a couple of conditions, above, under which this might happen.) if (remove) { if (previous == null) { this.NewestMessageAssember = assembler.NextOldestAssembler; } else { previous.NextOldestAssembler = assembler.NextOldestAssembler; } } // If an assembler was found which accepted the chunk, we're done. // (There are a couple of conditions, above, under which this might happen.) if (done) { if (complete) { yield return message; // The message has been completed, so flush any chunks which were waiting for it. foreach (object dependent in this.FlushWaitingChunks(chunk.MessageSequence)) yield return dependent; } yield break; } else { // Get the next assembler. Do not break from the loop if there // is no "next" assembler, since one will be created. previous = assembler; assembler = assembler.NextOldestAssembler; } } }
/// <summary> /// Writes a chunk to a data stream. /// </summary> public void EncodeChunk(Stream stream, Chunk chunk) { // FIXME: More efficient encoding of chunks might be possible here. // Custom encoding would require a Decode method to be used by RTPMessageReceiver. #if GENERIC_SERIALIZATION chunk.Serialize().WriteToStream( stream ); #else this.m_Formatter.Serialize( stream, chunk ); #endif }
/// <summary> /// Serialize the chunk then enqueue a send operation for each client socket for which there is a /// participant membership in the receivers group. /// </summary> /// <param name="chunk"></param> /// <param name="receivers"></param> public void Send(Chunk chunk, Group receivers, MessageTags tags) { using (MemoryStream stream = new MemoryStream((int)(this.m_Encoder.MaximumChunkSize * 2))) { stream.Position = 0; this.m_Encoder.EncodeChunk(stream, chunk); byte[] buffer = stream.GetBuffer(); int length = (int)stream.Length; //Unicast to multicast bridge #if RTP_BUILD if ((this.m_U2MBridge != null) && !(receivers is SingletonGroup)) { this.m_U2MBridge.Send(buffer,length,tags); } #endif lock (m_AllClients) { foreach (ClientData client in m_AllClients.Values) { if (client.Participant != null) { if (!client.Participant.Groups.Contains(receivers)) { if (receivers == Group.AllParticipant) { //Should never happen Trace.WriteLine("!!!Participant has lost membership in 'All Participants' (sending anyway). Client= " + client.Id.ToString(), this.GetType().ToString()); if (client.Socket != null) Trace.WriteLine(" Socket endpoint=" + client.Socket.RemoteEndPoint.ToString()); } else { //Ignoring participant (no group match) if (receivers is SingletonGroup) { //Trace.WriteLine("Ignoring participant; no group match for singleton group; participant= " + client.Id.ToString(), this.GetType().ToString()); } else Trace.WriteLine("Ignoring participant; no group match for group: " + receivers.FriendlyName + "; participant= " + client.Id.ToString(), this.GetType().ToString()); continue; } } } else { //This probably shouldn't ever happen Trace.WriteLine("Failed to find a participant for a connected socket.", this.GetType().ToString()); continue; } m_ServerSender.Send(buffer, length, client, tags, chunk.MessageSequence, chunk.ChunkSequenceInMessage,false); } } } }
public void Send(Chunk chunk) { using(MemoryStream ms = new MemoryStream()) { this.m_bf.Serialize(ms, chunk); this.m_Sender.Send(new BufferChunk(ms.GetBuffer(), 0, (int) ms.Length)); } }
/// <summary> /// Inserts the given chunk into the buffer. /// </summary> /// <remarks> /// <para> /// If the chunk's <see cref="Chunk.FrameSequence">frame sequence number</see> is /// higher than any previously inserted chunk, then <see cref="NewestRecoverableFrame"/> /// and <see cref="OldestRecoverableFrame"/> are updated to reflect the size of the buffer. /// Any chunks whose frame sequence numbers fall outside of that range are removed, and /// will no longer be recoverable under any circumnstances. /// </para> /// <para> /// On the other hand, if the chunk is older than <see cref="NewestRecoverableFrame"/>, /// then it is simply inserted into the buffer, unless it is also older than /// <see cref="OldestRecoverableFrame"/>, in which case it is discarded. /// </para> /// </remarks> /// <param name="value">The chunk to insert.</param> public void Insert(Chunk value) { if (value == null) throw new ArgumentNullException("value", "The data inserted into the buffer cannot be null."); // Compute the index into the buffer table where the new data will go. ulong index = value.FrameSequence % this.m_BufferSize; // If the chunk is not inserted in ascending order, then all we have to // do is insert it into the same index in the table as Take() would access. if (this.m_Newest >= value.FrameSequence && value.FrameSequence != 0) { if (this.m_Oldest <= value.FrameSequence && value.FrameSequence <= this.m_Newest) this.Set(index, value); return; } // Store the new chunk in the buffer. this.Set(index, value); // We must special-case the first time a chunk is buffered. if (this.m_First == ulong.MaxValue) { this.m_Oldest = this.m_First = this.m_Newest = value.FrameSequence; } else { // If the frame sequences are not sequential, we need to clean up invalid entries // that were not already overwritten. We use "++expired" to skip the most recent // valid entry. The "failsafe" ensures that no more than m_BufferSize iterations // are made (more wouldn't hurt but would be wasteful). The index is hashed into // the table each time in case it wraps around to the beginning. for (ulong failsafe = 0, expired = this.m_Newest; ++expired < value.FrameSequence && failsafe++ < this.m_BufferSize; ) this.Set(expired % this.m_BufferSize, null); // Regardless, we need to set the new m_Newest. Debug.Assert(value.FrameSequence > this.m_Newest); this.m_Newest = value.FrameSequence; // We need to find the new oldest entry. Until the first m_BufferSize entries // are added, the first entry cannot have been overwritten and it is the oldest. Debug.Assert(value.FrameSequence > this.m_First); if (value.FrameSequence - this.m_First < this.m_BufferSize) { this.m_Oldest = this.m_First; } // Otherwise the oldest entry must increase. If sequence numbers were skipped, // it may increase by more than one if additional entries are null. // The loop must terminate because the new, non-null entry was set above. else while (this.m_Buffer[++this.m_Oldest % this.m_BufferSize] == null) ; } }
public void Send(Chunk chunk) { this.Post(this.m_SendChunkDelegate, new object[] { chunk }); }
public NetworkChunkEvent(Chunk chunk, long timeIndex, string source) : base(timeIndex, source) { this.chunk = chunk; }