private static void Test() { // Make a Random instance with a fixed seed so that tests are repeatable. Random random = new Random(0); DisjointSet test = null; // Add a single range. DisjointSet.Add(ref test, new Range(5, 10)); // Verify that DisjointSet.Contains works for each value in the range. for(ulong i = 0; i <= 15; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (5 <= i && i <= 10)), "DisjointSet.Contains does not work (simple case)."); } // Verify that it works even when the set is splayed randomly. for(ulong j = 0; j <= 100; j++) { ulong i = (ulong) random.Next(0, 15); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (5 <= i && i <= 10)), "DisjointSet.Contains does not work (simple case, random splaying)."); } // Add new ranges that overlap the existing range(s). DisjointSet.Add(ref test, new Range(4, 9)); DisjointSet.Add(ref test, new Range(15, 20)); DisjointSet.Add(ref test, new Range(10, 15)); // Verify that the ranges were merged. Debug.Assert(test.Left == null && test.Right == null, "DisjointSet.Add did not merge overlapping ranges."); Debug.Assert(test.Value == new Range(4, 20), "DisjointSet.Add did not merge overlapping ranges."); // Add ranges adjacent to the ends of the existing range. DisjointSet.Add(ref test, new Range(21, 25)); DisjointSet.Add(ref test, 3ul); // Verify that they were also merged. Debug.Assert(test.Left == null && test.Right == null, "DisjointSet.Add did not merge adjacent ranges."); Debug.Assert(test.Value == new Range(3, 25), "DisjointSet.Add did not merge adjacent ranges."); // Remove a range. DisjointSet.Remove(ref test, new Range(10, 15)); // Verify that the ranges still work. for(ulong i = 0; i <= 30; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((3 <= i && i <= 9) || (16 <= i && i <= 25))), "DisjointSet.Contains does not work after removal."); } // Verify that it works even when the set is splayed randomly. for(ulong j = 0; j <= 300; j++) { ulong i = (ulong) random.Next(0, 30); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((3 <= i && i <= 9) || (16 <= i && i <= 25))), "DisjointSet.Contains does not work after removal (random splaying)."); } // Remove the entire split ranges (on a cloned copy so we can do it several times). DisjointSet empty; empty = test.Clone(); DisjointSet.Remove(ref empty, Range.UNIVERSE); Debug.Assert(empty == null, "DisjointSet.Remove(UNIVERSE) didn't result in an empty set."); empty = test.Clone(); DisjointSet.Remove(ref empty, new Range(3, 25)); Debug.Assert(empty == null, "DisjointSet.Remove of entire range didn't result in an empty set."); empty = test.Clone(); DisjointSet.Remove(ref empty, new Range(3, 26)); Debug.Assert(empty == null, "DisjointSet.Remove of entire range didn't result in an empty set."); empty = test.Clone(); DisjointSet.Remove(ref empty, new Range(2, 25)); Debug.Assert(empty == null, "DisjointSet.Remove of entire range didn't result in an empty set."); ///////////////////////////////////////////////////////////////////////// // Now reset the set so we can do some stress tests (mostly to make // sure the splaying code works correctly). test = null; // Insert all integers divisible by 5, 7, or 11. This makes the ranges // easily testable, and results in a set with some singletons as well // as some contiguous ranges. for(ulong i = 0; i < 1000; i++) { if(i % 5 == 0 || i % 7 == 0 || i % 11 == 0) { DisjointSet.Add(ref test, i); } } // Verify the contents of the set. for(ulong i = 0; i < 1000; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)), "DisjointSet.Contains failed the stress test."); } // Verify the contents of the set, even with random splaying. for(ulong j = 0; j < 10000; j++) { ulong i = (ulong) random.Next(0, 1000); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)), "DisjointSet.Contains failed the stress test (random splaying)."); } // Now remove all singletons divisible by 7. for(ulong i = 0; i < 1000; i++) { if(i % 7 == 0) { DisjointSet.Remove(ref test, i); } } // Verify the contents of the set. for(ulong i = 0; i < 1000; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))), "DisjointSet.Contains failed the stress test."); } // Verify the contents of the set, even with random splaying. for(ulong j = 0, i; j < 10000; j++) { i = (ulong) random.Next(0, 1000); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))), "DisjointSet.Contains failed the stress test (random splaying)."); } // Test the IEnumerator implementation. ArrayList enums = new ArrayList(); ulong prev = 0; foreach(ulong i in test) { Debug.Assert(i >= prev, "The enumeration is not well ordered."); enums.Add(prev = i); } // Verify that the output of the enumerator is exactly what should be in the set. for(ulong i = 0; i < 1000; i++) { Debug.Assert(!(enums.Contains(i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))), "The enumeration did not properly enumerate the set."); } // Test removing from a big tree. DisjointSet.Remove(ref test, Range.UNIVERSE); Debug.Assert(test == null, "DisjointSet.Remove(UNIVERSE) didn't yield an empty set."); ///////////////////////////////////////////////////////////////////////// // Reset the test and add a lot of small random ranges. This simulates // the kind of behavior experienced by RTPNetworkReceiver. test = null; ArrayList values = new ArrayList(10000); for(ulong i = 0; i < 10000; i++) { Range r = i; // Expand the range by some random number of indices. while(random.Next(2) == 1) r = new Range(r.Min, ++i); // Add all the indices in the range to both sets. DisjointSet.Add(ref test, r); for(ulong v = r.Min; v <= r.Max; v++) values.Add(v); // Skip some random number of indices. while(random.Next(2) == 1) i++; } // Test removing all of the values from the set in randomized ascending order, // just like what happens when receiving responses to a NACK (ie., some frames // might be dropped twice (or more times), and then NACKed again). DisjointSet copy = test.Clone(); for(ArrayList compare = (ArrayList) values.Clone(); compare.Count > 0;) { for(int i = 0; i < compare.Count; ) { Debug.Assert(copy != null, "DisjointSet is empty but not all entries have been removed."); // Skip some random number of entries. while(random.Next(2) == 1) i++; if(i >= compare.Count) break; // Remove the entries one at a time. ulong v = (ulong) compare[i]; DisjointSet.Remove(ref copy, v); compare.RemoveAt(i); } } Debug.Assert(copy == null, "All entries were removed but DisjointSet is not empty."); // Test that all of the entries were actually added to the set. foreach(ulong v in test) Debug.Assert(values.Contains(v), "DisjointSet contains a value that it shouldn't."); foreach(ulong v in values) Debug.Assert(DisjointSet.Contains(ref test, v), "DisjointSet doesn't contain a value that it should."); ///////////////////////////////////////////////////////////////////////// // Simulate an error-case discovered during actual testing. // See Bug 821, attachment #45 (comment #3). DisjointSet current = null; DisjointSet.Add(ref current, new Range(1, 4)); DisjointSet.Add(ref current, new Range(6, 16)); DisjointSet.Add(ref current, new Range(18, 20)); DisjointSet.Add(ref current, new Range(22, 33)); DisjointSet.Add(ref current, new Range(35, 55)); DisjointSet.Add(ref current, new Range(57, 61)); DisjointSet.Add(ref current, 64); DisjointSet.Add(ref current, 67); DisjointSet stable = null; DisjointSet.Add(ref stable, new Range(1, 16)); DisjointSet.Add(ref stable, new Range(18, 61)); DisjointSet.Add(ref stable, 64); DisjointSet.Add(ref stable, 67); copy = stable.Clone(); test = new Range(1, 100); DisjointSet.Remove(ref test, current); foreach (ulong i in test) DisjointSet.Remove(ref copy, i); DisjointSet.Remove(ref copy, 0); DisjointSet.Remove(ref copy, stable); Debug.Assert(copy == null, "Stable set is not a superset of the Current set."); }
private DisjointSet Insert(Range value) { checked { for(DisjointSet p = this, q; ; p = q) { // If the new range is strictly less than or greater than the // existing range and is not *adjacent*, descend down the left // or right subtree like a normal binary-tree search. If there // is no appropriate subtree, insert a brand new node. if(value.Max < p.Value.Min - (p.Value.Min > ulong.MinValue ? 1ul : 0ul)) { q = p.Left; if(q == null) { p.Left = new DisjointSet(null, value, null); p.Left.Parent = p; return p.Left; } } else if(value.Min > p.Value.Max + (p.Value.Max < ulong.MaxValue ? 1ul : 0ul)) { q = p.Right; if(q == null) { p.Right = new DisjointSet(null, value, null); p.Right.Parent = p; return p.Right; } } // But if the ranges overlap or are adjacent, we've found our mark. else { // Get the left and right subtrees. DisjointSet left = p.Left, right = p.Right; // Eliminate all subtrees that overlap or are adjacent to the new range. // Detach the subtrees so we can splay them. // Splay each subtree so the value for comparison will be at the top. // Each time, adjust the new range if the existing ranges extend the bounds. if (left != null) { do { DisjointSet parent = left.Parent; left.Parent = null; left = Splay(left.Find(value.Min)); left.Parent = Parent; if (value.Min <= left.Value.Max + (left.Value.Max < ulong.MaxValue ? 1ul : 0ul)) { value = new Range(Math.Min(value.Min, left.Value.Min), value.Max); left = left.Left; } else break; } while (left != null); } if (right != null) { do { DisjointSet parent = right.Parent; right.Parent = null; right = Splay(right.Find(value.Max)); right.Parent = parent; if (right.Value.Min - (right.Value.Min > ulong.MinValue ? 1ul : 0ul) <= value.Max) { value = new Range(value.Min, Math.Max(value.Max, right.Value.Max)); right = right.Right; } else break; } while (right != null); } // Now extend the bounds of the range at the root of the tree. p.Value = new Range(Math.Min(value.Min, p.Value.Min), Math.Max(value.Max, p.Value.Max)); // Reattach the eliminated subtrees' branches, which are now not adjacent to the new value. p.Left = left; p.Right = right; // Reattach their parents. if (left != null) { left.Parent = p; left.AssertWellOrdered(); } if (right != null) { right.Parent = p; right.AssertWellOrdered(); } // Return the "inserted" node. p.AssertWellOrdered(); return p; } } } }
public static void Remove(ref DisjointSet left, Range right) { checked { // If the set is already empty, do nothing. if(left == null) return; // We must first ensure that the range we want to remove is present // in the set as a single, contiguous range. (This could be done // without this step but would be much more complicated.) Fortunately, // the Add operation already does this for us via Insert. // Add also splays the new range to the root of the tree. Add(ref left, right); Debug.Assert(left.Value.Min <= right.Min && left.Value.Max >= right.Max, "The Add operation did not leave the new range at the root."); // If the range at the root is now completely equal to the range to remove, // all we have to do is then delete it with a standard Splay Tree deletion. if(right == left.Value) { if(left.Left != null) { // First completely remove the existing root from the tree. if(left.Left != null) left.Left.Parent = null; if(left.Right != null) left.Right.Parent = null; // Then find the root's predecessor, which will have no right subtree. DisjointSet pred = Splay(left.Left.FindMax()); Debug.Assert(pred.Right == null); Debug.Assert(pred.Parent == null); // Then attach the old right subtree. pred.Right = left.Right; if(pred.Right != null) pred.Right.Parent = pred; // Finally, "return" the new root. left = pred; } else { // If there is no predecessor, the right subtree (if any) becomes the root, // and the old root is thrown away completely. left = left.Right; if(left != null) left.Parent = null; } } // Otherwise, the range to remove is a subset of the existing contiguous range, // which we have to split depending on which portion is affected. else if(left.Value.Min == right.Min) { // If one of the endpoints of the range to remove is the same as the root // range, all we have to do is adjust the range. Debug.Assert(right.Max < left.Value.Max); left.Value = new Range(right.Max + 1, left.Value.Max); } else if(left.Value.Max == right.Max) { // This is the same case but for the other endpoint. Debug.Assert(left.Value.Min < right.Min); left.Value = new Range(left.Value.Min, right.Min - 1); } else { // If neither endpoint is equal, we have to make a "hole" in the middle, // which creates an additional node in the tree. Debug.Assert(left.Value.Max > right.Max); Debug.Assert(left.Value.Min < right.Min); Range existing = left.Value; // The new node will be the existing node's successor, so we can insert // it manually as the new right subtree of the existing node. left.Value = new Range(existing.Min, right.Min - 1); left.Right = new DisjointSet(null, new Range(right.Max + 1, existing.Max), left.Right); left.Right.Parent = left; if(left.Right.Right != null) left.Right.Right.Parent = left.Right; // Finally, splay the new node to the root and "return" it. left = Splay(left.Right); } if(left != null) { Debug.Assert(left.Parent == null); left.AssertWellOrdered(); } } }
public static void Add(ref DisjointSet left, Range right) { // An add is a normal binary tree insertion, // followed by a splay of the inserted node to the root. left = (left == null) ? right : Splay(left.Insert(right)); left.AssertWellOrdered(); }
private DisjointSet(DisjointSet left, Range value, DisjointSet right) { this.Left = left; this.Value = value; this.Right = right; }
/// <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; }