コード例 #1
0
ファイル: DisjointSet.cs プロジェクト: ClassroomPresenter/CP3
        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.");
        }
コード例 #2
0
ファイル: DisjointSet.cs プロジェクト: ClassroomPresenter/CP3
        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;
                    }
                }
            }
        }
コード例 #3
0
ファイル: DisjointSet.cs プロジェクト: ClassroomPresenter/CP3
        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();
                }
            }
        }
コード例 #4
0
ファイル: DisjointSet.cs プロジェクト: ClassroomPresenter/CP3
 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();
 }
コード例 #5
0
ファイル: DisjointSet.cs プロジェクト: ClassroomPresenter/CP3
 private DisjointSet(DisjointSet left, Range value, DisjointSet right)
 {
     this.Left = left;
     this.Value = value;
     this.Right = right;
 }
コード例 #6
0
        /// <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;
        }