diff --git a/lib/0Harmony.dll b/lib/0Harmony.dll deleted file mode 100644 index eec6135..0000000 Binary files a/lib/0Harmony.dll and /dev/null differ diff --git a/lib/BepInEx.Core.dll b/lib/BepInEx.6.IL2CPP/BepInEx.Core.dll similarity index 99% rename from lib/BepInEx.Core.dll rename to lib/BepInEx.6.IL2CPP/BepInEx.Core.dll index c518afe..c708484 100644 Binary files a/lib/BepInEx.Core.dll and b/lib/BepInEx.6.IL2CPP/BepInEx.Core.dll differ diff --git a/lib/BepInEx.IL2CPP.dll b/lib/BepInEx.6.IL2CPP/BepInEx.IL2CPP.dll similarity index 98% rename from lib/BepInEx.IL2CPP.dll rename to lib/BepInEx.6.IL2CPP/BepInEx.IL2CPP.dll index f3ab8ca..f087db6 100644 Binary files a/lib/BepInEx.IL2CPP.dll and b/lib/BepInEx.6.IL2CPP/BepInEx.IL2CPP.dll differ diff --git a/lib/BepInEx.6.Mono/BepInEx.Core.dll b/lib/BepInEx.6.Mono/BepInEx.Core.dll new file mode 100644 index 0000000..35205cd Binary files /dev/null and b/lib/BepInEx.6.Mono/BepInEx.Core.dll differ diff --git a/lib/BepInEx.6.Mono/BepInEx.Unity.dll b/lib/BepInEx.6.Mono/BepInEx.Unity.dll new file mode 100644 index 0000000..1ac65ac Binary files /dev/null and b/lib/BepInEx.6.Mono/BepInEx.Unity.dll differ diff --git a/lib/BepInEx.Unity.dll b/lib/BepInEx.Unity.dll deleted file mode 100644 index c26ad65..0000000 Binary files a/lib/BepInEx.Unity.dll and /dev/null differ diff --git a/lib/INIFileParser.dll b/lib/INIFileParser.dll deleted file mode 100644 index d19e920..0000000 Binary files a/lib/INIFileParser.dll and /dev/null differ diff --git a/lib/UnhollowerBaseLib.dll b/lib/UnhollowerBaseLib.dll deleted file mode 100644 index b34956e..0000000 Binary files a/lib/UnhollowerBaseLib.dll and /dev/null differ diff --git a/lib/UnityEngine.UI.dll b/lib/UnityEngine.UI.dll deleted file mode 100644 index cf7c37e..0000000 Binary files a/lib/UnityEngine.UI.dll and /dev/null differ diff --git a/lib/UnityEngine.dll b/lib/UnityEngine.dll deleted file mode 100644 index 29d73f6..0000000 Binary files a/lib/UnityEngine.dll and /dev/null differ diff --git a/lib/mcs.dll b/lib/mcs.dll deleted file mode 100644 index e83df45..0000000 Binary files a/lib/mcs.dll and /dev/null differ diff --git a/lib/publicized_assemblies/UnityEngine.UI.dll b/lib/mono/UnityEngine.UI.dll similarity index 100% rename from lib/publicized_assemblies/UnityEngine.UI.dll rename to lib/mono/UnityEngine.UI.dll diff --git a/lib/publicized_assemblies/UnityEngine.dll b/lib/mono/UnityEngine.dll similarity index 100% rename from lib/publicized_assemblies/UnityEngine.dll rename to lib/mono/UnityEngine.dll diff --git a/src/Core/Runtime/Mono/SortedSet.cs b/src/Core/Runtime/Mono/SortedSet.cs new file mode 100644 index 0000000..0b0f396 --- /dev/null +++ b/src/Core/Runtime/Mono/SortedSet.cs @@ -0,0 +1,3107 @@ +#if MONO +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; +using System.Runtime.CompilerServices; + +// Reimplementation of SortedSet and required supplementary classes for .NET 3.5 +// https://referencesource.microsoft.com/#System/compmod/system/collections/generic/sortedset.cs + +namespace System.Collections.Generic +{ + #region ISet + + /// + /// Generic collection that guarantees the uniqueness of its elements, as defined + /// by some comparer. It also supports basic set operations such as Union, Intersection, + /// Complement and Exclusive Complement. + /// + public interface ISet : ICollection + { + //Add ITEM to the set, return true if added, false if duplicate + new bool Add(T item); + + //Transform this set into its union with the IEnumerable other + void UnionWith(IEnumerable other); + + //Transform this set into its intersection with the IEnumberable other + void IntersectWith(IEnumerable other); + + //Transform this set so it contains no elements that are also in other + void ExceptWith(IEnumerable other); + + //Transform this set so it contains elements initially in this or in other, but not both + void SymmetricExceptWith(IEnumerable other); + + //Check if this set is a subset of other + bool IsSubsetOf(IEnumerable other); + + //Check if this set is a superset of other + bool IsSupersetOf(IEnumerable other); + + //Check if this set is a subset of other, but not the same as it + bool IsProperSupersetOf(IEnumerable other); + + //Check if this set is a superset of other, but not the same as it + bool IsProperSubsetOf(IEnumerable other); + + //Check if this set has any elements in common with other + bool Overlaps(IEnumerable other); + + //Check if this set contains the same and only the same elements as other + bool SetEquals(IEnumerable other); + } + + #endregion + + + #region Bit Helper + + /// + /// ABOUT: + /// Helps with operations that rely on bit marking to indicate whether an item in the + /// collection should be added, removed, visited already, etc. + /// + /// BitHelper doesn't allocate the array; you must pass in an array or ints allocated on the + /// stack or heap. ToIntArrayLength() tells you the int array size you must allocate. + /// + /// USAGE: + /// Suppose you need to represent a bit array of length (i.e. logical bit array length) + /// BIT_ARRAY_LENGTH. Then this is the suggested way to instantiate BitHelper: + /// *************************************************************************** + /// int intArrayLength = BitHelper.ToIntArrayLength(BIT_ARRAY_LENGTH); + /// BitHelper bitHelper; + /// if (intArrayLength less than stack alloc threshold) + /// int* m_arrayPtr = stackalloc int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// else + /// int[] m_arrayPtr = new int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// *************************************************************************** + /// + /// IMPORTANT: + /// The second ctor args, length, should be specified as the length of the int array, not + /// the logical bit array. Because length is used for bounds checking into the int array, + /// it's especially important to get this correct for the stackalloc version. See the code + /// samples above; this is the value gotten from ToIntArrayLength(). + /// + /// The length ctor argument is the only exception; for other methods -- MarkBit and + /// IsMarked -- pass in values as indices into the logical bit array, and it will be mapped + /// to the position within the array of ints. + /// + /// + + unsafe internal class BitHelper + { // should not be serialized + + private const byte MarkedBitFlag = 1; + private const byte IntSize = 32; + + // m_length of underlying int array (not logical bit array) + private int m_length; + + // ptr to stack alloc'd array of ints + [System.Security.SecurityCritical] + private int* m_arrayPtr; + + // array of ints + private int[] m_array; + + // whether to operate on stack alloc'd or heap alloc'd array + private bool useStackAlloc; + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + // + // + // + // + [System.Security.SecurityCritical] + internal BitHelper(int* bitArrayPtr, int length) + { + this.m_arrayPtr = bitArrayPtr; + this.m_length = length; + useStackAlloc = true; + } + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + internal BitHelper(int[] bitArray, int length) + { + this.m_array = bitArray; + this.m_length = length; + } + + /// + /// Mark bit at specified position + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe void MarkBit(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_arrayPtr[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_array[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + } + + /// + /// Is bit at specified position marked? + /// + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe bool IsMarked(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_arrayPtr[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_array[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + } + + /// + /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but + /// avoids overflow + /// + /// + /// + internal static int ToIntArrayLength(int n) + { + return n > 0 ? ((n - 1) / IntSize + 1) : 0; + } + + } + + #endregion + + + #region SortedSet + + /*============================================================ + ** + ** Class: SortedSet + ** + ** Purpose: A generic sorted set. + ** + ** Date: August 15, 2008 + ** + ===========================================================*/ + + // + // A binary search tree is a red-black tree if it satisfies the following red-black properties: + // 1. Every node is either red or black + // 2. Every leaf (nil node) is black + // 3. If a node is red, then both its children are black + // 4. Every simple path from a node to a descendant leaf contains the same number of black nodes + // + // The basic idea of red-black tree is to represent 2-3-4 trees as standard BSTs but to add one extra bit of information + // per node to encode 3-nodes and 4-nodes. + // 4-nodes will be represented as: B + // R R + // 3 -node will be represented as: B or B + // R B B R + // + // For a detailed description of the algorithm, take a look at "Algorithms" by Robert Sedgewick. + // + + internal delegate bool TreeWalkPredicate(SortedSet.Node node); + + internal enum TreeRotation + { + LeftRotation = 1, + RightRotation = 2, + RightLeftRotation = 3, + LeftRightRotation = 4, + } + + //[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "by design name choice")] + [DebuggerDisplay("Count = {Count}")] +#if !FEATURE_NETCORE + [Serializable] + public class SortedSet : ISet, ICollection, ICollection, ISerializable, IDeserializationCallback + { +#else + public class SortedSet : ISet, ICollection, ICollection, IReadOnlyCollection { +#endif //!FEATURE_NETCORE + #region local variables/constants + Node root; + IComparer comparer; + int count; + int version; + private Object _syncRoot; + + private const String ComparerName = "Comparer"; + private const String CountName = "Count"; + private const String ItemsName = "Items"; + private const String VersionName = "Version"; + //needed for enumerator + private const String TreeName = "Tree"; + private const String NodeValueName = "Item"; + private const String EnumStartName = "EnumStarted"; + private const String ReverseName = "Reverse"; + private const String EnumVersionName = "EnumVersion"; + +#if !FEATURE_NETCORE + //needed for TreeSubset + private const String minName = "Min"; + private const String maxName = "Max"; + private const String lBoundActiveName = "lBoundActive"; + private const String uBoundActiveName = "uBoundActive"; + + private SerializationInfo siInfo; //A temporary variable which we need during deserialization. +#endif + internal const int StackAllocThreshold = 100; + + #endregion + + #region Constructors + public SortedSet() + { + this.comparer = Comparer.Default; + + } + + public SortedSet(IComparer comparer) + { + if (comparer == null) + { + this.comparer = Comparer.Default; + } + else + { + this.comparer = comparer; + } + } + + + public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) { } + + public SortedSet(IEnumerable collection, IComparer comparer) + : this(comparer) + { + + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + // these are explicit type checks in the mould of HashSet. It would have worked better + // with something like an ISorted (we could make this work for SortedList.Keys etc) + SortedSet baseSortedSet = collection as SortedSet; + SortedSet baseTreeSubSet = collection as TreeSubSet; + if (baseSortedSet != null && baseTreeSubSet == null && AreComparersEqual(this, baseSortedSet)) + { + //breadth first traversal to recreate nodes + if (baseSortedSet.Count == 0) + { + count = 0; + version = 0; + root = null; + return; + } + + + //pre order way to replicate nodes + Stack theirStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); + Stack myStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); + Node theirCurrent = baseSortedSet.root; + Node myCurrent = (theirCurrent != null ? new SortedSet.Node(theirCurrent.Item, theirCurrent.IsRed) : null); + root = myCurrent; + while (theirCurrent != null) + { + theirStack.Push(theirCurrent); + myStack.Push(myCurrent); + myCurrent.Left = (theirCurrent.Left != null ? new SortedSet.Node(theirCurrent.Left.Item, theirCurrent.Left.IsRed) : null); + theirCurrent = theirCurrent.Left; + myCurrent = myCurrent.Left; + } + while (theirStack.Count != 0) + { + theirCurrent = theirStack.Pop(); + myCurrent = myStack.Pop(); + Node theirRight = theirCurrent.Right; + Node myRight = null; + if (theirRight != null) + { + myRight = new SortedSet.Node(theirRight.Item, theirRight.IsRed); + } + myCurrent.Right = myRight; + + while (theirRight != null) + { + theirStack.Push(theirRight); + myStack.Push(myRight); + myRight.Left = (theirRight.Left != null ? new SortedSet.Node(theirRight.Left.Item, theirRight.Left.IsRed) : null); + theirRight = theirRight.Left; + myRight = myRight.Left; + } + } + count = baseSortedSet.count; + version = 0; + } + else + { //As it stands, you're doing an NlogN sort of the collection + + List els = new List(collection); + els.Sort(this.comparer); + for (int i = 1; i < els.Count; i++) + { + if (comparer.Compare(els[i], els[i - 1]) == 0) + { + els.RemoveAt(i); + i--; + } + } + root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null); + count = els.Count; + version = 0; + } + } + + +#if !FEATURE_NETCORE + protected SortedSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + } +#endif + #endregion + + #region Bulk Operation Helpers + private void AddAllElements(IEnumerable collection) + { + + foreach (T item in collection) + { + if (!this.Contains(item)) + Add(item); + } + } + + private void RemoveAllElements(IEnumerable collection) + { + T min = this.Min; + T max = this.Max; + foreach (T item in collection) + { + if (!(comparer.Compare(item, min) < 0 || comparer.Compare(item, max) > 0) && this.Contains(item)) + this.Remove(item); + } + } + + private bool ContainsAllElements(IEnumerable collection) + { + foreach (T item in collection) + { + if (!this.Contains(item)) + { + return false; + } + } + return true; + } + + // + // Do a in order walk on tree and calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal bool InOrderTreeWalk(TreeWalkPredicate action) + { + return InOrderTreeWalk(action, false); + } + + // Allows for the change in traversal direction. Reverse visits nodes in descending order + internal virtual bool InOrderTreeWalk(TreeWalkPredicate action, bool reverse) + { + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + // note: this should be logbase2, but since the stack grows itself, we + // don't want the extra cost + Stack stack = new Stack(2 * (int)(SortedSet.log2(Count + 1))); + Node current = root; + while (current != null) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + } + return true; + } + + // + // Do a left to right breadth first walk on tree and + // calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + if (root == null) + { + return true; + } + + List processQueue = new List(); + processQueue.Add(root); + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (!action(current)) + { + return false; + } + if (current.Left != null) + { + processQueue.Add(current.Left); + } + if (current.Right != null) + { + processQueue.Add(current.Right); + } + } + return true; + } + #endregion + + #region Properties + public int Count + { + get + { + VersionCheck(); + return count; + } + } + + public IComparer Comparer + { + get + { + return comparer; + } + } + + bool ICollection.IsReadOnly + { + get + { + return false; + } + } + + bool ICollection.IsSynchronized + { + get + { + return false; + } + } + + object ICollection.SyncRoot + { + get + { + if (_syncRoot == null) + { + System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + #endregion + + #region Subclass helpers + + //virtual function for subclass that needs to update count + internal virtual void VersionCheck() { } + + + //virtual function for subclass that needs to do range checks + internal virtual bool IsWithinRange(T item) + { + return true; + + } + #endregion + + #region ICollection Members + /// + /// Add the value ITEM to the tree, returns true if added, false if duplicate + /// + /// item to be added + public bool Add(T item) + { + return AddIfNotPresent(item); + } + + void ICollection.Add(T item) + { + AddIfNotPresent(item); + } + + + /// + /// Adds ITEM to the tree if not already present. Returns TRUE if value was successfully added + /// or FALSE if it is a duplicate + /// + internal virtual bool AddIfNotPresent(T item) + { + if (root == null) + { // empty tree + root = new Node(item, false); + count = 1; + version++; + return true; + } + + // + // Search for a node at bottom to insert the new node. + // If we can guanratee the node we found is not a 4-node, it would be easy to do insertion. + // We split 4-nodes along the search path. + // + Node current = root; + Node parent = null; + Node grandParent = null; + Node greatGrandParent = null; + + //even if we don't actually add to the set, we may be altering its structure (by doing rotations + //and such). so update version to disable any enumerators/subsets working on it + version++; + + + int order = 0; + while (current != null) + { + order = comparer.Compare(item, current.Item); + if (order == 0) + { + // We could have changed root node to red during the search process. + // We need to set it to black before we return. + root.IsRed = false; + return false; + } + + // split a 4-node into two 2-nodes + if (Is4Node(current)) + { + Split4Node(current); + // We could have introduced two consecutive red nodes after split. Fix that by rotation. + if (IsRed(parent)) + { + InsertionBalance(current, ref parent, grandParent, greatGrandParent); + } + } + greatGrandParent = grandParent; + grandParent = parent; + parent = current; + current = (order < 0) ? current.Left : current.Right; + } + + Debug.Assert(parent != null, "Parent node cannot be null here!"); + // ready to insert the new node + Node node = new Node(item); + if (order > 0) + { + parent.Right = node; + } + else + { + parent.Left = node; + } + + // the new node will be red, so we will need to adjust the colors if parent node is also red + if (parent.IsRed) + { + InsertionBalance(node, ref parent, grandParent, greatGrandParent); + } + + // Root node is always black + root.IsRed = false; + ++count; + return true; + } + + /// + /// Remove the T ITEM from this SortedSet. Returns true if successfully removed. + /// + /// + /// + public bool Remove(T item) + { + return this.DoRemove(item); // hack so it can be made non-virtual + } + + internal virtual bool DoRemove(T item) + { + + if (root == null) + { + return false; + } + + + // Search for a node and then find its succesor. + // Then copy the item from the succesor to the matching node and delete the successor. + // If a node doesn't have a successor, we can replace it with its left child (if not empty.) + // or delete the matching node. + // + // In top-down implementation, it is important to make sure the node to be deleted is not a 2-node. + // Following code will make sure the node on the path is not a 2 Node. + + //even if we don't actually remove from the set, we may be altering its structure (by doing rotations + //and such). so update version to disable any enumerators/subsets working on it + version++; + + Node current = root; + Node parent = null; + Node grandParent = null; + Node match = null; + Node parentOfMatch = null; + bool foundMatch = false; + while (current != null) + { + if (Is2Node(current)) + { // fix up 2-Node + if (parent == null) + { // current is root. Mark it as red + current.IsRed = true; + } + else + { + Node sibling = GetSibling(current, parent); + if (sibling.IsRed) + { + // If parent is a 3-node, flip the orientation of the red link. + // We can acheive this by a single rotation + // This case is converted to one of other cased below. + Debug.Assert(!parent.IsRed, "parent must be a black node!"); + if (parent.Right == sibling) + { + RotateLeft(parent); + } + else + { + RotateRight(parent); + } + + parent.IsRed = true; + sibling.IsRed = false; // parent's color + // sibling becomes child of grandParent or root after rotation. Update link from grandParent or root + ReplaceChildOfNodeOrRoot(grandParent, parent, sibling); + // sibling will become grandParent of current node + grandParent = sibling; + if (parent == match) + { + parentOfMatch = sibling; + } + + // update sibling, this is necessary for following processing + sibling = (parent.Left == current) ? parent.Right : parent.Left; + } + Debug.Assert(sibling != null || sibling.IsRed == false, "sibling must not be null and it must be black!"); + + if (Is2Node(sibling)) + { + Merge2Nodes(parent, current, sibling); + } + else + { + // current is a 2-node and sibling is either a 3-node or a 4-node. + // We can change the color of current to red by some rotation. + TreeRotation rotation = RotationNeeded(parent, current, sibling); + Node newGrandParent = null; + switch (rotation) + { + case TreeRotation.RightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + sibling.Left.IsRed = false; + newGrandParent = RotateRight(parent); + break; + case TreeRotation.LeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + sibling.Right.IsRed = false; + newGrandParent = RotateLeft(parent); + break; + + case TreeRotation.RightLeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + newGrandParent = RotateRightLeft(parent); + break; + + case TreeRotation.LeftRightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + newGrandParent = RotateLeftRight(parent); + break; + } + + newGrandParent.IsRed = parent.IsRed; + parent.IsRed = false; + current.IsRed = true; + ReplaceChildOfNodeOrRoot(grandParent, parent, newGrandParent); + if (parent == match) + { + parentOfMatch = newGrandParent; + } + grandParent = newGrandParent; + } + } + } + + // we don't need to compare any more once we found the match + int order = foundMatch ? -1 : comparer.Compare(item, current.Item); + if (order == 0) + { + // save the matching node + foundMatch = true; + match = current; + parentOfMatch = parent; + } + + grandParent = parent; + parent = current; + + if (order < 0) + { + current = current.Left; + } + else + { + current = current.Right; // continue the search in right sub tree after we find a match + } + } + + // move successor to the matching node position and replace links + if (match != null) + { + ReplaceNode(match, parentOfMatch, parent, grandParent); + --count; + } + + if (root != null) + { + root.IsRed = false; + } + return foundMatch; + } + + public virtual void Clear() + { + root = null; + count = 0; + ++version; + } + + + public virtual bool Contains(T item) + { + + return FindNode(item) != null; + } + + + + + public void CopyTo(T[] array) { CopyTo(array, 0, Count); } + + public void CopyTo(T[] array, int index) { CopyTo(array, index, Count); } + + public void CopyTo(T[] array, int index, int count) + { + if (array == null) + throw new ArgumentNullException("array"); + + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + // will array, starting at arrayIndex, be able to hold elements? Note: not + // checking arrayIndex >= array.Length (consistency with list of allowing + // count of 0; subsequent check takes care of the rest) + if (index > array.Length || count > array.Length - index) + throw new ArgumentException("count and index"); + + //upper bound + count += index; + + InOrderTreeWalk(delegate (Node node) + { + if (index >= count) + { + return false; + } + else + { + array[index++] = node.Item; + return true; + } + }); + } + + void ICollection.CopyTo(Array array, int index) + { + // TODO THROWS + + //if (array == null) + //{ + // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + //} + // + //if (array.Rank != 1) + //{ + // ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + //} + // + //if (array.GetLowerBound(0) != 0) + //{ + // ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + //} + // + //if (index < 0) + //{ + // ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + //} + // + //if (array.Length - index < Count) + //{ + // ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + //} + + T[] tarray = array as T[]; + if (tarray != null) + { + CopyTo(tarray, index); + } + else + { + object[] objects = array as object[]; + //if (objects == null) + //{ + // ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + //} + + try + { + InOrderTreeWalk(delegate (Node node) { objects[index++] = node.Item; return true; }); + } + catch (ArrayTypeMismatchException) + { + //ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + // TODO THROW + } + } + } + + #endregion + + #region IEnumerable members + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + + + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + #endregion + + #region Tree Specific Operations + + private static Node GetSibling(Node node, Node parent) + { + if (parent.Left == node) + { + return parent.Right; + } + return parent.Left; + } + + // After calling InsertionBalance, we need to make sure current and parent up-to-date. + // It doesn't matter if we keep grandParent and greatGrantParent up-to-date + // because we won't need to split again in the next node. + // By the time we need to split again, everything will be correctly set. + // + private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent) + { + Debug.Assert(grandParent != null, "Grand parent cannot be null here!"); + bool parentIsOnRight = (grandParent.Right == parent); + bool currentIsOnRight = (parent.Right == current); + + Node newChildOfGreatGrandParent; + if (parentIsOnRight == currentIsOnRight) + { // same orientation, single rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeft(grandParent) : RotateRight(grandParent); + } + else + { // different orientaton, double rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent); + // current node now becomes the child of greatgrandparent + parent = greatGrandParent; + } + // grand parent will become a child of either parent of current. + grandParent.IsRed = true; + newChildOfGreatGrandParent.IsRed = false; + + ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent); + } + + private static bool Is2Node(Node node) + { + Debug.Assert(node != null, "node cannot be null!"); + return IsBlack(node) && IsNullOrBlack(node.Left) && IsNullOrBlack(node.Right); + } + + private static bool Is4Node(Node node) + { + return IsRed(node.Left) && IsRed(node.Right); + } + + private static bool IsBlack(Node node) + { + return (node != null && !node.IsRed); + } + + private static bool IsNullOrBlack(Node node) + { + return (node == null || !node.IsRed); + } + + private static bool IsRed(Node node) + { + return (node != null && node.IsRed); + } + + private static void Merge2Nodes(Node parent, Node child1, Node child2) + { + Debug.Assert(IsRed(parent), "parent must be be red"); + // combing two 2-nodes into a 4-node + parent.IsRed = false; + child1.IsRed = true; + child2.IsRed = true; + } + + // Replace the child of a parent node. + // If the parent node is null, replace the root. + private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild) + { + if (parent != null) + { + if (parent.Left == child) + { + parent.Left = newChild; + } + else + { + parent.Right = newChild; + } + } + else + { + root = newChild; + } + } + + // Replace the matching node with its succesor. + private void ReplaceNode(Node match, Node parentOfMatch, Node succesor, Node parentOfSuccesor) + { + if (succesor == match) + { // this node has no successor, should only happen if right child of matching node is null. + Debug.Assert(match.Right == null, "Right child must be null!"); + succesor = match.Left; + } + else + { + Debug.Assert(parentOfSuccesor != null, "parent of successor cannot be null!"); + Debug.Assert(succesor.Left == null, "Left child of succesor must be null!"); + Debug.Assert((succesor.Right == null && succesor.IsRed) || (succesor.Right.IsRed && !succesor.IsRed), "Succesor must be in valid state"); + if (succesor.Right != null) + { + succesor.Right.IsRed = false; + } + + if (parentOfSuccesor != match) + { // detach succesor from its parent and set its right child + parentOfSuccesor.Left = succesor.Right; + succesor.Right = match.Right; + } + + succesor.Left = match.Left; + } + + if (succesor != null) + { + succesor.IsRed = match.IsRed; + } + + ReplaceChildOfNodeOrRoot(parentOfMatch, match, succesor); + + } + + internal virtual Node FindNode(T item) + { + Node current = root; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return current; + } + else + { + current = (order < 0) ? current.Left : current.Right; + } + } + + return null; + } + + //used for bithelpers. Note that this implementation is completely different + //from the Subset's. The two should not be mixed. This indexes as if the tree were an array. + //http://en.wikipedia.org/wiki/Binary_Tree#Methods_for_storing_binary_trees + internal virtual int InternalIndexOf(T item) + { + Node current = root; + int count = 0; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return count; + } + else + { + current = (order < 0) ? current.Left : current.Right; + count = (order < 0) ? (2 * count + 1) : (2 * count + 2); + } + } + return -1; + } + + + + internal Node FindRange(T from, T to) + { + return FindRange(from, to, true, true); + } + internal Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive) + { + Node current = root; + while (current != null) + { + if (lowerBoundActive && comparer.Compare(from, current.Item) > 0) + { + current = current.Right; + } + else + { + if (upperBoundActive && comparer.Compare(to, current.Item) < 0) + { + current = current.Left; + } + else + { + return current; + } + } + } + + return null; + } + + internal void UpdateVersion() + { + ++version; + } + + + private static Node RotateLeft(Node node) + { + Node x = node.Right; + node.Right = x.Left; + x.Left = node; + return x; + } + + private static Node RotateLeftRight(Node node) + { + Node child = node.Left; + Node grandChild = child.Right; + + node.Left = grandChild.Right; + grandChild.Right = node; + child.Right = grandChild.Left; + grandChild.Left = child; + return grandChild; + } + + private static Node RotateRight(Node node) + { + Node x = node.Left; + node.Left = x.Right; + x.Right = node; + return x; + } + + private static Node RotateRightLeft(Node node) + { + Node child = node.Right; + Node grandChild = child.Left; + + node.Right = grandChild.Left; + grandChild.Left = node; + child.Left = grandChild.Right; + grandChild.Right = child; + return grandChild; + } + /// + /// Testing counter that can track rotations + /// + + + private static TreeRotation RotationNeeded(Node parent, Node current, Node sibling) + { + Debug.Assert(IsRed(sibling.Left) || IsRed(sibling.Right), "sibling must have at least one red child"); + if (IsRed(sibling.Left)) + { + if (parent.Left == current) + { + return TreeRotation.RightLeftRotation; + } + return TreeRotation.RightRotation; + } + else + { + if (parent.Left == current) + { + return TreeRotation.LeftRotation; + } + return TreeRotation.LeftRightRotation; + } + } + + /// + /// Used for deep equality of SortedSet testing + /// + /// + public static IEqualityComparer> CreateSetComparer() + { + return new SortedSetEqualityComparer(); + } + + /// + /// Create a new set comparer for this set, where this set's members' equality is defined by the + /// memberEqualityComparer. Note that this equality comparer's definition of equality must be the + /// same as this set's Comparer's definition of equality + /// + public static IEqualityComparer> CreateSetComparer(IEqualityComparer memberEqualityComparer) + { + return new SortedSetEqualityComparer(memberEqualityComparer); + } + + + /// + /// Decides whether these sets are the same, given the comparer. If the EC's are the same, we can + /// just use SetEquals, but if they aren't then we have to manually check with the given comparer + /// + internal static bool SortedSetEquals(SortedSet set1, SortedSet set2, IComparer comparer) + { + // handle null cases first + if (set1 == null) + { + return (set2 == null); + } + else if (set2 == null) + { + // set1 != null + return false; + } + + if (AreComparersEqual(set1, set2)) + { + if (set1.Count != set2.Count) + return false; + + return set1.SetEquals(set2); + } + else + { + bool found = false; + foreach (T item1 in set1) + { + found = false; + foreach (T item2 in set2) + { + if (comparer.Compare(item1, item2) == 0) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; + } + + } + + + //This is a little frustrating because we can't support more sorted structures + private static bool AreComparersEqual(SortedSet set1, SortedSet set2) + { + return set1.Comparer.Equals(set2.Comparer); + } + + + private static void Split4Node(Node node) + { + node.IsRed = true; + node.Left.IsRed = false; + node.Right.IsRed = false; + } + + /// + /// Copies this to an array. Used for DebugView + /// + /// + internal T[] ToArray() + { + T[] newArray = new T[Count]; + CopyTo(newArray); + return newArray; + } + + + #endregion + + #region ISet Members + + /// + /// Transform this set into its union with the IEnumerable OTHER + ///Attempts to insert each element and rejects it if it exists. + /// NOTE: The caller object is important as UnionWith uses the Comparator + ///associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void UnionWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + + if (t != null) + VersionCheck(); + + if (s != null && t == null && this.count == 0) + { + SortedSet dummy = new SortedSet(s, this.comparer); + this.root = dummy.root; + this.count = dummy.count; + this.version++; + return; + } + + + if (s != null && t == null && AreComparersEqual(this, s) && (s.Count > this.Count / 2)) + { //this actually hurts if N is much greater than M the /2 is arbitrary + //first do a merge sort to an array. + T[] merged = new T[s.Count + this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + merged[c++] = mine.Current; + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + merged[c++] = theirs.Current; + theirsEnded = !theirs.MoveNext(); + } + } + + if (!mineEnded || !theirsEnded) + { + Enumerator remaining = (mineEnded ? theirs : mine); + do + { + merged[c++] = remaining.Current; + } while (remaining.MoveNext()); + } + + //now merged has all c elements + + //safe to gc the root, we have all the elements + root = null; + + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + AddAllElements(other); + } + } + + + private static Node ConstructRootFromSortedArray(T[] arr, int startIndex, int endIndex, Node redNode) + { + + + + //what does this do? + //you're given a sorted array... say 1 2 3 4 5 6 + //2 cases: + // If there are odd # of elements, pick the middle element (in this case 4), and compute + // its left and right branches + // If there are even # of elements, pick the left middle element, save the right middle element + // and call the function on the rest + // 1 2 3 4 5 6 -> pick 3, save 4 and call the fn on 1,2 and 5,6 + // now add 4 as a red node to the lowest element on the right branch + // 3 3 + // 1 5 -> 1 5 + // 2 6 2 4 6 + // As we're adding to the leftmost of the right branch, nesting will not hurt the red-black properties + // Leaf nodes are red if they have no sibling (if there are 2 nodes or if a node trickles + // down to the bottom + + + //the iterative way to do this ends up wasting more space than it saves in stack frames (at + //least in what i tried) + //so we're doing this recursively + //base cases are described below + int size = endIndex - startIndex + 1; + if (size == 0) + { + return null; + } + Node root = null; + if (size == 1) + { + root = new Node(arr[startIndex], false); + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 2) + { + root = new Node(arr[startIndex], false); + root.Right = new Node(arr[endIndex], false); + root.Right.IsRed = true; + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 3) + { + root = new Node(arr[startIndex + 1], false); + root.Left = new Node(arr[startIndex], false); + root.Right = new Node(arr[endIndex], false); + if (redNode != null) + { + root.Left.Left = redNode; + + } + } + else + { + int midpt = ((startIndex + endIndex) / 2); + root = new Node(arr[midpt], false); + root.Left = ConstructRootFromSortedArray(arr, startIndex, midpt - 1, redNode); + if (size % 2 == 0) + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 2, endIndex, new Node(arr[midpt + 1], true)); + } + else + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 1, endIndex, null); + } + } + return root; + + } + + + /// + /// Transform this set into its intersection with the IEnumerable OTHER + /// NOTE: The caller object is important as IntersectionWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public virtual void IntersectWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return; + + //HashSet optimizations can't be done until equality comparers and comparers are related + + //Technically, this would work as well with an ISorted + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + if (t != null) + VersionCheck(); + //only let this happen if i am also a SortedSet, not a SubSet + if (s != null && t == null && AreComparersEqual(this, s)) + { + + + //first do a merge sort to an array. + T[] merged = new T[this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + T max = Max; + T min = Min; + + while (!mineEnded && !theirsEnded && Comparer.Compare(theirs.Current, max) <= 0) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + theirsEnded = !theirs.MoveNext(); + } + } + + //now merged has all c elements + + //safe to gc the root, we have all the elements + root = null; + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + IntersectWithEnumerable(other); + } + } + + internal virtual void IntersectWithEnumerable(IEnumerable other) + { + // + List toSave = new List(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + AddAllElements(toSave); + + } + + + + /// + /// Transform this set into its complement with the IEnumerable OTHER + /// NOTE: The caller object is important as ExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void ExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (count == 0) + return; + + if (other == this) + { + this.Clear(); + return; + } + + SortedSet asSorted = other as SortedSet; + + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + //outside range, no point doing anything + if (!(comparer.Compare(asSorted.Max, this.Min) < 0 || comparer.Compare(asSorted.Min, this.Max) > 0)) + { + T min = this.Min; + T max = this.Max; + foreach (T item in other) + { + if (comparer.Compare(item, min) < 0) + continue; + if (comparer.Compare(item, max) > 0) + break; + Remove(item); + } + } + + } + else + { + RemoveAllElements(other); + } + } + + /// + /// Transform this set so it contains elements in THIS or OTHER but not both + /// NOTE: The caller object is important as SymmetricExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void SymmetricExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count == 0) + { + this.UnionWith(other); + return; + } + + if (other == this) + { + this.Clear(); + return; + } + + + SortedSet asSorted = other as SortedSet; + +#if USING_HASH_SET + HashSet asHash = other as HashSet; +#endif + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + SymmetricExceptWithSameEC(asSorted); + } +#if USING_HASH_SET + else if (asHash != null && this.comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + SymmetricExceptWithSameEC(asHash); + } +#endif + else + { + //need perf improvement on this + T[] elements = (new List(other)).ToArray(); + Array.Sort(elements, this.Comparer); + SymmetricExceptWithSameEC(elements); + } + } + + //OTHER must be a set + internal void SymmetricExceptWithSameEC(ISet other) + { + foreach (T item in other) + { + //yes, it is classier to say + //if (!this.Remove(item))this.Add(item); + //but this ends up saving on rotations + if (this.Contains(item)) + { + this.Remove(item); + } + else + { + this.Add(item); + } + } + } + + //OTHER must be a sorted array + internal void SymmetricExceptWithSameEC(T[] other) + { + if (other.Length == 0) + { + return; + } + T last = other[0]; + for (int i = 0; i < other.Length; i++) + { + while (i < other.Length && i != 0 && comparer.Compare(other[i], last) == 0) + i++; + if (i >= other.Length) + break; + if (this.Contains(other[i])) + { + this.Remove(other[i]); + } + else + { + this.Add(other[i]); + } + last = other[i]; + } + } + + + /// + /// Checks whether this Tree is a subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return true; + + + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count > asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + else + { + //worst case: mark every element in my set and see if i've counted all + //O(MlogN) + + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount >= 0); + } + } + + private bool IsSubsetOfSortedSetWithSameEC(SortedSet asSorted) + { + SortedSet prunedOther = asSorted.GetViewBetween(this.Min, this.Max); + foreach (T item in this) + { + if (!prunedOther.Contains(item)) + return false; + } + return true; + + } + + + /// + /// Checks whether this Tree is a proper subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if ((other as ICollection) != null) + { + if (Count == 0) + return (other as ICollection).Count > 0; + } + + +#if USING_HASH_SET + //do it one way for HashSets + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSupersetOf(this); + } +#endif + //another for sorted sets with the same comparer + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count >= asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + + + //worst case: mark every element in my set and see if i've counted all + //O(MlogN). + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount > 0); + } + + + /// + /// Checks whether this Tree is a super set of the IEnumerable other + /// + /// + /// + public bool IsSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + + //do it one way for HashSets +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsSubsetOf(this); + } +#endif + //another for sorted sets with the same comparer + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count < asSorted.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + //and a third for everything else + return ContainsAllElements(other); + } + + /// + /// Checks whether this Tree is a proper super set of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return false; + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + +#if USING_HASH_SET + //do it one way for HashSets + + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSubsetOf(this); + } +#endif + //another way for sorted sets + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(asSorted, this)) + { + if (asSorted.Count >= this.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + + + //worst case: mark every element in my set and see if i've counted all + //O(MlogN) + //slight optimization, put it into a HashSet and then check can do it in O(N+M) + //but slower in better cases + wastes space + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount < Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has all elements in common with IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool SetEquals(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.SetEquals(this); + } +#endif + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + IEnumerator mine = this.GetEnumerator(); + IEnumerator theirs = asSorted.GetEnumerator(); + bool mineEnded = !mine.MoveNext(); + bool theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + if (Comparer.Compare(mine.Current, theirs.Current) != 0) + { + return false; + } + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + return mineEnded && theirsEnded; + } + + //worst case: mark every element in my set and see if i've counted all + //O(N) by size of other + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount == Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has any elements in common with IEnumerable other + /// + /// + /// + public bool Overlaps(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count == 0) + return false; + + if ((other as ICollection != null) && (other as ICollection).Count == 0) + return false; + + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted) && (comparer.Compare(Min, asSorted.Max) > 0 || comparer.Compare(Max, asSorted.Min) < 0)) + { + return false; + } +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.Overlaps(this); + } +#endif + foreach (T item in other) + { + if (this.Contains(item)) + { + return true; + } + } + return false; + } + + /// + /// This works similar to HashSet's CheckUniqueAndUnfound (description below), except that the bit + /// array maps differently than in the HashSet. We can only use this for the bulk boolean checks. + /// + /// Determines counts that can be used to determine equality, subset, and superset. This + /// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet + /// these properties can be checked faster without use of marking because we can assume + /// other has no duplicates. + /// + /// The following count checks are performed by callers: + /// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = Count; i.e. everything + /// in other is in this and everything in this is in other + /// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = Count; i.e. other may + /// have elements not in this and everything in this is in other + /// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = Count; i.e + /// other must have at least one element not in this and everything in this is in other + /// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less + /// than Count; i.e. everything in other was in this and this had at least one element + /// not contained in other. + /// + /// An earlier implementation used delegates to perform these checks rather than returning + /// an ElementCount struct; however this was changed due to the perf overhead of delegates. + /// + /// + /// Allows us to finish faster for equals and proper superset + /// because unfoundCount must be 0. + /// + // + // + // + // + // + // + [System.Security.SecurityCritical] + private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) + { + ElementCount result; + + // need special case in case this has no elements. + if (Count == 0) + { + int numElementsInOther = 0; + foreach (T item in other) + { + numElementsInOther++; + // break right away, all we want to know is whether other has 0 or 1 elements + break; + } + result.uniqueCount = 0; + result.unfoundCount = numElementsInOther; + return result; + } + + + int originalLastIndex = Count; + int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex); + + BitHelper bitHelper; + if (intArrayLength <= StackAllocThreshold) + { + int* bitArrayPtr = stackalloc int[intArrayLength]; + bitHelper = new BitHelper(bitArrayPtr, intArrayLength); + } + else + { + int[] bitArray = new int[intArrayLength]; + bitHelper = new BitHelper(bitArray, intArrayLength); + } + + // count of items in other not found in this + int unfoundCount = 0; + // count of unique items in other found in this + int uniqueFoundCount = 0; + + foreach (T item in other) + { + int index = InternalIndexOf(item); + if (index >= 0) + { + if (!bitHelper.IsMarked(index)) + { + // item hasn't been seen yet + bitHelper.MarkBit(index); + uniqueFoundCount++; + } + } + else + { + unfoundCount++; + if (returnIfUnfound) + { + break; + } + } + } + + result.uniqueCount = uniqueFoundCount; + result.unfoundCount = unfoundCount; + return result; + } + public int RemoveWhere(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException("match"); + } + List matches = new List(this.Count); + + BreadthFirstTreeWalk(delegate (Node n) + { + if (match(n.Item)) + { + matches.Add(n.Item); + } + return true; + }); + // reverse breadth first to (try to) incur low cost + int actuallyRemoved = 0; + for (int i = matches.Count - 1; i >= 0; i--) + { + if (this.Remove(matches[i])) + { + actuallyRemoved++; + } + } + + return actuallyRemoved; + + } + + + #endregion + + #region ISorted Members + + + public T Min + { + get + { + T ret = default(T); + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }); + return ret; + } + } + + public T Max + { + get + { + T ret = default(T); + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }, true); + return ret; + } + } + + public IEnumerable Reverse() + { + Enumerator e = new Enumerator(this, true); + while (e.MoveNext()) + { + yield return e.Current; + } + } + + + /// + /// Returns a subset of this tree ranging from values lBound to uBound + /// Any changes made to the subset reflect in the actual tree + /// + /// Lowest Value allowed in the subset + /// Highest Value allowed in the subset + public virtual SortedSet GetViewBetween(T lowerValue, T upperValue) + { + if (Comparer.Compare(lowerValue, upperValue) > 0) + { + throw new ArgumentException("lowerBound is greater than upperBound"); + } + return new TreeSubSet(this, lowerValue, upperValue, true, true); + } + +#if DEBUG + + /// + /// debug status to be checked whenever any operation is called + /// + /// + internal virtual bool versionUpToDate() { + return true; + } +#endif + + + /// + /// This class represents a subset view into the tree. Any changes to this view + /// are reflected in the actual tree. Uses the Comparator of the underlying tree. + /// + /// +#if !FEATURE_NETCORE + [Serializable] + internal sealed class TreeSubSet : SortedSet, ISerializable, IDeserializationCallback + { +#else + internal sealed class TreeSubSet : SortedSet { +#endif + SortedSet underlying; + T min, max; + //these exist for unbounded collections + //for instance, you could allow this subset to be defined for i>10. The set will throw if + //anything <=10 is added, but there is no upperbound. These features Head(), Tail(), were punted + //in the spec, and are not available, but the framework is there to make them available at some point. + bool lBoundActive, uBoundActive; + //used to see if the count is out of date + + +#if DEBUG + internal override bool versionUpToDate() { + return (this.version == underlying.version); + } +#endif + + public TreeSubSet(SortedSet Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive) + : base(Underlying.Comparer) + { + underlying = Underlying; + min = Min; + max = Max; + lBoundActive = lowerBoundActive; + uBoundActive = upperBoundActive; + root = underlying.FindRange(min, max, lBoundActive, uBoundActive); // root is first element within range + count = 0; + version = -1; + VersionCheckImpl(); + } + +#if !FEATURE_NETCORE + /// + /// For serialization and deserialization + /// + private TreeSubSet() + { + comparer = null; + } + + + [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", Justification = "special case TreeSubSet serialization")] + private TreeSubSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + OnDeserializationImpl(info); + } +#endif // !FEATURE_NETCORE + + /// + /// Additions to this tree need to be added to the underlying tree as well + /// + + internal override bool AddIfNotPresent(T item) + { + + if (!IsWithinRange(item)) + { + throw new ArgumentOutOfRangeException("collection"); + } + + bool ret = underlying.AddIfNotPresent(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + + return ret; + } + + + public override bool Contains(T item) + { + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.Contains(item); + } + + internal override bool DoRemove(T item) + { // todo: uppercase this and others + + if (!IsWithinRange(item)) + { + return false; + } + + bool ret = underlying.Remove(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return ret; + } + + public override void Clear() + { + + + if (count == 0) + { + return; + } + + List toRemove = new List(); + BreadthFirstTreeWalk(delegate (Node n) { toRemove.Add(n.Item); return true; }); + while (toRemove.Count != 0) + { + underlying.Remove(toRemove[toRemove.Count - 1]); + toRemove.RemoveAt(toRemove.Count - 1); + } + root = null; + count = 0; + version = underlying.version; + } + + + internal override bool IsWithinRange(T item) + { + + int comp = (lBoundActive ? Comparer.Compare(min, item) : -1); + if (comp > 0) + { + return false; + } + comp = (uBoundActive ? Comparer.Compare(max, item) : 1); + if (comp < 0) + { + return false; + } + return true; + } + + internal override bool InOrderTreeWalk(TreeWalkPredicate action, Boolean reverse) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + Stack stack = new Stack(2 * (int)SortedSet.log2(count + 1)); //this is not exactly right if count is out of date, but the stack can grow + Node current = root; + while (current != null) + { + if (IsWithinRange(current.Item)) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + else if (lBoundActive && Comparer.Compare(min, current.Item) > 0) + { + current = current.Right; + } + else + { + current = current.Left; + } + } + + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + if (IsWithinRange(node.Item)) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + else if (lBoundActive && Comparer.Compare(min, node.Item) > 0) + { + node = node.Right; + } + else + { + node = node.Left; + } + } + } + return true; + } + + internal override bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + List processQueue = new List(); + processQueue.Add(root); + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (IsWithinRange(current.Item) && !action(current)) + { + return false; + } + if (current.Left != null && (!lBoundActive || Comparer.Compare(min, current.Item) < 0)) + { + processQueue.Add(current.Left); + } + if (current.Right != null && (!uBoundActive || Comparer.Compare(max, current.Item) > 0)) + { + processQueue.Add(current.Right); + } + + } + return true; + } + + internal override SortedSet.Node FindNode(T item) + { + + if (!IsWithinRange(item)) + { + return null; + } + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.FindNode(item); + } + + //this does indexing in an inefficient way compared to the actual sortedset, but it saves a + //lot of space + internal override int InternalIndexOf(T item) + { + int count = -1; + foreach (T i in this) + { + count++; + if (Comparer.Compare(item, i) == 0) + return count; + } +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return -1; + } + /// + /// checks whether this subset is out of date. updates if necessary. + /// + internal override void VersionCheck() + { + VersionCheckImpl(); + } + + private void VersionCheckImpl() + { + Debug.Assert(underlying != null, "Underlying set no longer exists"); + if (this.version != underlying.version) + { + this.root = underlying.FindRange(min, max, lBoundActive, uBoundActive); + this.version = underlying.version; + count = 0; + InOrderTreeWalk(delegate (Node n) { count++; return true; }); + } + } + + + + //This passes functionality down to the underlying tree, clipping edges if necessary + //There's nothing gained by having a nested subset. May as well draw it from the base + //Cannot increase the bounds of the subset, can only decrease it + public override SortedSet GetViewBetween(T lowerValue, T upperValue) + { + + if (lBoundActive && Comparer.Compare(min, lowerValue) > 0) + { + //lBound = min; + throw new ArgumentOutOfRangeException("lowerValue"); + } + if (uBoundActive && Comparer.Compare(max, upperValue) < 0) + { + //uBound = max; + throw new ArgumentOutOfRangeException("upperValue"); + } + TreeSubSet ret = (TreeSubSet)underlying.GetViewBetween(lowerValue, upperValue); + return ret; + } + + internal override void IntersectWithEnumerable(IEnumerable other) + { + + List toSave = new List(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + this.AddAllElements(toSave); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + } + +#if !FEATURE_NETCORE + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + info.AddValue(maxName, max, typeof(T)); + info.AddValue(minName, min, typeof(T)); + info.AddValue(lBoundActiveName, lBoundActive); + info.AddValue(uBoundActiveName, uBoundActive); + base.GetObjectData(info, context); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + //don't do anything here as its already been done by the constructor + //OnDeserialization(sender); + + } + + protected override void OnDeserialization(Object sender) + { + OnDeserializationImpl(sender); + } + + private void OnDeserializationImpl(Object sender) + { + if (siInfo == null) + { + throw new SerializationException("Invalid deserialization"); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + max = (T)siInfo.GetValue(maxName, typeof(T)); + min = (T)siInfo.GetValue(minName, typeof(T)); + lBoundActive = siInfo.GetBoolean(lBoundActiveName); + uBoundActive = siInfo.GetBoolean(uBoundActiveName); + underlying = new SortedSet(); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + throw new SerializationException("Missing values"); + } + + for (int i = 0; i < items.Length; i++) + { + underlying.Add(items[i]); + } + } + underlying.version = siInfo.GetInt32(VersionName); + count = underlying.count; + version = underlying.version - 1; + VersionCheck(); //this should update the count to be right and update root to be right + + if (count != savedCount) + { + throw new SerializationException("Mismatched count"); + } + siInfo = null; + + } +#endif // !FEATURE_NETCORE + } + + + #endregion + + #region Serialization methods + +#if !FEATURE_NETCORE + // LinkDemand here is unnecessary as this is a methodimpl and linkdemand from the interface should suffice + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentException("info"); + } + + info.AddValue(CountName, count); //This is the length of the bucket array. + info.AddValue(ComparerName, comparer, typeof(IComparer)); + info.AddValue(VersionName, version); + + if (root != null) + { + T[] items = new T[Count]; + CopyTo(items, 0); + info.AddValue(ItemsName, items, typeof(T[])); + } + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + protected virtual void OnDeserialization(Object sender) + { + if (comparer != null) + { + return; //Somebody had a dependency on this class and fixed us up before the ObjectManager got to it. + } + + if (siInfo == null) + { + throw new SerializationException("invalid on deserialize"); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + throw new SerializationException("Missing values"); + + } + + for (int i = 0; i < items.Length; i++) + { + Add(items[i]); + } + } + + version = siInfo.GetInt32(VersionName); + if (count != savedCount) + { + throw new SerializationException("Mismatched count"); + } + siInfo = null; + } +#endif //!FEATURE_NETCORE + #endregion + + #region Helper Classes + internal class Node + { + public bool IsRed; + public T Item; + public Node Left; + public Node Right; + + public Node(T item) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + IsRed = true; + } + + public Node(T item, bool isRed) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + this.IsRed = isRed; + } + } + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] +#if !FEATURE_NETCORE + [Serializable] + public struct Enumerator : IEnumerator, IEnumerator, ISerializable, IDeserializationCallback + { +#else + public struct Enumerator : IEnumerator, IEnumerator { +#endif + private SortedSet tree; + private int version; + + + private Stack.Node> stack; + private SortedSet.Node current; + static SortedSet.Node dummyNode = new SortedSet.Node(default(T)); + + private bool reverse; + +#if !FEATURE_NETCORE + private SerializationInfo siInfo; +#endif + internal Enumerator(SortedSet set) + { + tree = set; + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1)); + current = null; + reverse = false; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + } + + internal Enumerator(SortedSet set, bool reverse) + { + tree = set; + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1)); + current = null; + this.reverse = reverse; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + + } + +#if !FEATURE_NETCORE + private Enumerator(SerializationInfo info, StreamingContext context) + { + tree = null; + version = -1; + current = null; + reverse = false; + stack = null; + this.siInfo = info; + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + private void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + + } + info.AddValue(TreeName, tree, typeof(SortedSet)); + info.AddValue(EnumVersionName, version); + info.AddValue(ReverseName, reverse); + info.AddValue(EnumStartName, !NotStartedOrEnded); + info.AddValue(NodeValueName, (current == null ? dummyNode.Item : current.Item), typeof(T)); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + private void OnDeserialization(Object sender) + { + if (siInfo == null) + { + throw new SerializationException("invalid on deserialize"); + } + + tree = (SortedSet)siInfo.GetValue(TreeName, typeof(SortedSet)); + version = siInfo.GetInt32(EnumVersionName); + reverse = siInfo.GetBoolean(ReverseName); + bool EnumStarted = siInfo.GetBoolean(EnumStartName); + stack = new Stack.Node>(2 * (int)SortedSet.log2(tree.Count + 1)); + current = null; + if (EnumStarted) + { + T item = (T)siInfo.GetValue(NodeValueName, typeof(T)); + Intialize(); + //go until it reaches the value we want + while (this.MoveNext()) + { + if (tree.Comparer.Compare(this.Current, item) == 0) + break; + } + } + + + } +#endif //!FEATURE_NETCORE + + + private void Intialize() + { + + current = null; + SortedSet.Node node = tree.root; + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (next == null || !tree.IsWithinRange(next.Item)) + { + node = other; + } + else + { + node = next; + } + } + } + + public bool MoveNext() + { + + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + if (version != tree.version) + { + throw new Exception("Collection has been modified during enumeration"); + } + + if (stack.Count == 0) + { + current = null; + return false; + } + + current = stack.Pop(); + SortedSet.Node node = (reverse ? current.Left : current.Right); + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (other == null || !tree.IsWithinRange(other.Item)) + { + node = next; + } + else + { + node = other; + } + } + return true; + } + + public void Dispose() + { + } + + public T Current + { + get + { + if (current != null) + { + return current.Item; + } + return default(T); + } + } + + object IEnumerator.Current + { + get + { + if (current == null) + { + //ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + throw new InvalidOperationException("Current is null"); + } + + return current.Item; + } + } + + internal bool NotStartedOrEnded + { + get + { + return current == null; + } + } + + internal void Reset() + { + if (version != tree.version) + { + throw new Exception("Collection has been modified during enumeration"); + } + + stack.Clear(); + Intialize(); + } + + void IEnumerator.Reset() + { + Reset(); + } + + + + + } + + + + internal struct ElementCount + { + internal int uniqueCount; + internal int unfoundCount; + } + #endregion + + #region misc + + /// + /// Searches the set for a given value and returns the equal value it finds, if any. + /// + /// The value to search for. + /// The value from the set that the search found, or the default value of when the search yielded no match. + /// A value indicating whether the search was successful. + /// + /// This can be useful when you want to reuse a previously stored reference instead of + /// a newly constructed one (so that more sharing of references can occur) or to look up + /// a value that has more complete data than the value you currently have, although their + /// comparer functions indicate they are equal. + /// + public bool TryGetValue(T equalValue, out T actualValue) + { + Node node = FindNode(equalValue); + if (node != null) + { + actualValue = node.Item; + return true; + } + actualValue = default(T); + return false; + } + + // used for set checking operations (using enumerables) that rely on counting + private static int log2(int value) + { + //Contract.Requires(value>0) + int c = 0; + while (value > 0) + { + c++; + value >>= 1; + } + return c; + } + #endregion + + + } + + /// + /// A class that generates an IEqualityComparer for this SortedSet. Requires that the definition of + /// equality defined by the IComparer for this SortedSet be consistent with the default IEqualityComparer + /// for the type T. If not, such an IEqualityComparer should be provided through the constructor. + /// + internal class SortedSetEqualityComparer : IEqualityComparer> + { + private IComparer comparer; + private IEqualityComparer e_comparer; + + public SortedSetEqualityComparer() : this(null, null) { } + + public SortedSetEqualityComparer(IComparer comparer) : this(comparer, null) { } + + public SortedSetEqualityComparer(IEqualityComparer memberEqualityComparer) : this(null, memberEqualityComparer) { } + + /// + /// Create a new SetEqualityComparer, given a comparer for member order and another for member equality (these + /// must be consistent in their definition of equality) + /// + public SortedSetEqualityComparer(IComparer comparer, IEqualityComparer memberEqualityComparer) + { + if (comparer == null) + this.comparer = Comparer.Default; + else + this.comparer = comparer; + if (memberEqualityComparer == null) + e_comparer = EqualityComparer.Default; + else + e_comparer = memberEqualityComparer; + } + + + // using comparer to keep equals properties in tact; don't want to choose one of the comparers + public bool Equals(SortedSet x, SortedSet y) + { + return SortedSet.SortedSetEquals(x, y, comparer); + } + //IMPORTANT: this part uses the fact that GetHashCode() is consistent with the notion of equality in + //the set + public int GetHashCode(SortedSet obj) + { + int hashCode = 0; + if (obj != null) + { + foreach (T t in obj) + { + hashCode = hashCode ^ (e_comparer.GetHashCode(t) & 0x7FFFFFFF); + } + } // else returns hashcode of 0 for null HashSets + return hashCode; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) + { + SortedSetEqualityComparer comparer = obj as SortedSetEqualityComparer; + if (comparer == null) + { + return false; + } + return (this.comparer == comparer.comparer); + } + + public override int GetHashCode() + { + return comparer.GetHashCode() ^ e_comparer.GetHashCode(); + } + + + } + + #endregion +} + +#endif \ No newline at end of file diff --git a/src/ILRepack.targets b/src/ILRepack.targets index 607abe2..0c368c0 100644 --- a/src/ILRepack.targets +++ b/src/ILRepack.targets @@ -4,14 +4,20 @@ - - + + + + + + + + diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 73f90ea..1109f21 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -75,8 +75,6 @@ UnityExplorer.BIE5.Mono false true - none - false @@ -105,7 +103,7 @@ - ..\lib\mcs.dll + ..\lib\mcs-unity\mcs\bin\Release\mcs.dll False @@ -120,10 +118,10 @@ False - + - ..\lib\0Harmony.dll + ..\lib\HarmonyX\Harmony\bin\Release\net35\0Harmony.dll False @@ -137,47 +135,47 @@ - ..\lib\BepInEx.Core.dll + ..\lib\BepInEx.6.Mono\BepInEx.Core.dll False - ..\lib\BepInEx.Unity.dll + ..\lib\BepInEx.6.Mono\BepInEx.Unity.dll False - ..\lib\BepInEx.Core.dll + ..\lib\BepInEx.6.IL2CPP\BepInEx.Core.dll False - ..\lib\BepInEx.IL2CPP.dll + ..\lib\BepInEx.6.IL2CPP\BepInEx.IL2CPP.dll False - ..\lib\0Harmony.dll + ..\lib\HarmonyX\Harmony\bin\Release\net35\0Harmony.dll False - ..\lib\publicized_assemblies\UnityEngine.dll + ..\lib\mono\UnityEngine.dll False - ..\lib\publicized_assemblies\UnityEngine.UI.dll + ..\lib\mono\UnityEngine.UI.dll False - ..\lib\UnhollowerBaseLib.dll + ..\lib\Il2CppAssemblyUnhollower\UnhollowerBaseLib\bin\Release\net4.7.2\UnhollowerBaseLib.dll False @@ -219,6 +217,8 @@ + + @@ -312,7 +312,7 @@ - + diff --git a/src/UnityExplorer.sln b/src/UnityExplorer.sln index d71f046..ddc140d 100644 --- a/src/UnityExplorer.sln +++ b/src/UnityExplorer.sln @@ -3,6 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30128.74 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harmony", "..\lib\HarmonyX\Harmony\Harmony.csproj", "{F2D7872C-5D4D-49EB-A656-C3D496DB4204}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnhollowerBaseLib", "..\lib\Il2CppAssemblyUnhollower\UnhollowerBaseLib\UnhollowerBaseLib.csproj", "{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcs", "..\lib\mcs-unity\mcs\mcs.csproj", "{E4989E4C-0875-4528-9031-08E2C0E70103}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityExplorer", "UnityExplorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}" EndProject Global @@ -16,6 +22,48 @@ Global Release_STANDALONE_Mono|Any CPU = Release_STANDALONE_Mono|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_BIE_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_BIE_Cpp|Any CPU.Build.0 = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_BIE5_Mono|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_BIE5_Mono|Any CPU.Build.0 = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_BIE6_Mono|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_BIE6_Mono|Any CPU.Build.0 = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_ML_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU + {F2D7872C-5D4D-49EB-A656-C3D496DB4204}.Release_STANDALONE_Mono|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_BIE_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_BIE_Cpp|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_BIE5_Mono|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_BIE5_Mono|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_BIE6_Mono|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_BIE6_Mono|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU + {7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Mono|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_BIE_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_BIE_Cpp|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_BIE5_Mono|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_BIE5_Mono|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_BIE6_Mono|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_BIE6_Mono|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU + {E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Mono|Any CPU.Build.0 = Release|Any CPU {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.ActiveCfg = Release_BIE_Cpp|Any CPU {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.Build.0 = Release_BIE_Cpp|Any CPU {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE5_Mono|Any CPU.ActiveCfg = Release_BIE5_Mono|Any CPU