Sharpmake/UniqueList.cs (421 lines of code) (raw):

// Copyright (c) Ubisoft. All Rights Reserved. // Licensed under the Apache 2.0 License. See LICENSE.md in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading; namespace Sharpmake { /// <summary> /// Same as Strings with configurable type /// </summary> /// <typeparam name="T"></typeparam> [DebuggerDisplay("Size: {_hash.Count}")] public class UniqueList<T> : IEnumerable<T> { private HashSet<T> _hash; // key is the string private List<T> _values = new List<T>(); // Sorted keys are sorted on demand. [Flags] enum ModifierBits { DirtyBit = 1, // Does _values need to be reconstructed ? SortedBit = 2, // Is _values sorted ? ReadOnlyBit = 4, // Is the container read only ? } // Note: Sadly we have to use Int64 because Interlocked.Read only accepts a 64 bits value private Int64 _modifierBits = (Int64) ModifierBits.SortedBit; private bool IsDirty { get { Int64 modifierBits = Interlocked.Read(ref _modifierBits); return (modifierBits & (Int64)ModifierBits.DirtyBit) != 0; } set { if (value) { Interlocked.Or(ref _modifierBits, (Int64)ModifierBits.DirtyBit); } else { Interlocked.And(ref _modifierBits, ~(Int64)ModifierBits.DirtyBit); } } } private bool IsSorted { get { Int64 modifierBits = Interlocked.Read(ref _modifierBits); return (modifierBits & (Int64)ModifierBits.SortedBit) != 0; } set { if (value) { Interlocked.Or(ref _modifierBits, (Int64)ModifierBits.SortedBit); } else { Interlocked.And(ref _modifierBits, ~(Int64)ModifierBits.DirtyBit); } } } private IComparer<T> _sortComparer = Comparer<T>.Default; public UniqueList() : this(EqualityComparer<T>.Default) { } public UniqueList(IComparer<T> sortComparer) : this(EqualityComparer<T>.Default) { _sortComparer = sortComparer; } public UniqueList(IEqualityComparer<T> comparer) { _hash = new HashSet<T>(comparer); } public UniqueList(IEqualityComparer<T> comparer, IComparer<T> sortComparer) : this(comparer) { _sortComparer = sortComparer; } public UniqueList(IEqualityComparer<T> comparer, IEnumerable<T> other) : this(comparer) { AddRange(other); } public UniqueList(IEqualityComparer<T> comparer, params T[] values) : this(comparer) { AddRange(values); } public UniqueList(IEqualityComparer<T> comparer, UniqueList<T> other) : this(comparer) { _hash = new HashSet<T>(other._hash, comparer); IsDirty = other.Count > 0; } public void UpdateValue(T oldValue, T newValue) { if (!oldValue.Equals(newValue)) { _hash.Remove(oldValue); _hash.Add(newValue); IsDirty = true; } } private void AddCore(T value) { _hash.Add(value); } private void GrowCapacity(int by) { _hash.EnsureCapacity(_hash.Count + by); } public void Add(T value1) { ValidateReadOnly(); AddCore(value1); IsDirty = true; } public void Add(T value1, T value2) { ValidateReadOnly(); AddCore(value1); AddCore(value2); IsDirty = true; } public void Add(T value1, T value2, T value3) { ValidateReadOnly(); AddCore(value1); AddCore(value2); AddCore(value3); IsDirty = true; } public void Add(T value1, T value2, T value3, T value4) { ValidateReadOnly(); AddCore(value1); AddCore(value2); AddCore(value3); AddCore(value4); IsDirty = true; } public void Add(T value1, T value2, T value3, T value4, T value5) { ValidateReadOnly(); AddCore(value1); AddCore(value2); AddCore(value3); AddCore(value4); AddCore(value5); IsDirty = true; } public void Add(params T[] values) { if (values.Length > 0) { AddRange(values); } } public void AddRange(IEnumerable<T> collection) { ValidateReadOnly(); int countBeforeUnion = _hash.Count; _hash.UnionWith(collection); if (_hash.Count != countBeforeUnion) IsDirty = true; } public void AddRange(UniqueList<T> other) { ValidateReadOnly(); if (other.Count > 0) { GrowCapacity(other.Count); foreach (T value in other._hash) { AddCore(value); } IsDirty = true; } } public void AddRange(IReadOnlyList<T> collection) { ValidateReadOnly(); if (collection.Count > 0) { GrowCapacity(collection.Count); for (int i = 0; i < collection.Count; i++) { AddCore(collection[i]); } IsDirty = true; } } public void IntersectWith(UniqueList<T> otherList) { var shortList = _hash.Count <= otherList._hash.Count ? this : otherList; var longList = _hash.Count > otherList._hash.Count ? this : otherList; var intersect = new HashSet<T>(_hash.Comparer); foreach (T key in shortList._hash) { if (longList._hash.Contains(key)) { // it is important to avoid using the .Add function on the UniqueList<T> rest because we want to preserve the original callerInfo in the dictionary intersect.Add(key); } } _hash = intersect; IsDirty = true; } /// <param name="otherList">the other container to intersect with</param> /// <param name="rest">Contains elements in both containers that are did not intersect</param> public void IntersectWith(UniqueList<T> otherList, UniqueList<T> rest) { var shortList = _hash.Count <= otherList._hash.Count ? this : otherList; var longList = _hash.Count > otherList._hash.Count ? this : otherList; var intersect = new HashSet<T>(_hash.Comparer); foreach (T key in shortList._hash) { if (longList._hash.Contains(key)) { intersect.Add(key); } else { rest.Add(key); } } foreach (T key in longList._hash) { if (!shortList._hash.Contains(key)) { rest.Add(key); } } _hash = intersect; IsDirty = true; } public int RemoveAll(Predicate<T> match) { ValidateReadOnly(); int result = _hash.RemoveWhere(match); IsDirty |= result > 0; return result; } public void RemoveRange(IEnumerable<T> collection) { ValidateReadOnly(); bool isDirty = false; foreach (T item in collection) { isDirty |= _hash.Remove(item); } IsDirty |= isDirty; } public void Remove(params T[] values) { RemoveRange(values); } public IComparer<T> SortComparer { get { return _sortComparer; } } private void UpdateValues() { lock (_values) { if (IsDirty) { if (_values.Count > 0) _values.Clear(); if (_hash.Count > 0) _values.AddRange(_hash); // Clear boths bits in one operation Interlocked.And(ref _modifierBits, ~((Int64)ModifierBits.DirtyBit | (Int64)ModifierBits.SortedBit)); } } Debug.Assert(Count == _values.Count); } private void UpdateAndSortValues() { lock (_values) { // Directly check the modifiers bits to save one interlocked read Int64 modifiers = Interlocked.Read(ref _modifierBits); if ((modifiers & (Int64)ModifierBits.DirtyBit) != 0) { modifiers &= ~(Int64)ModifierBits.SortedBit; // Clear sorted bits locally if (_values.Count > 0) _values.Clear(); if (_hash.Count > 0) _values.AddRange(_hash); } if ((modifiers & (Int64)ModifierBits.SortedBit) == 0) { if (_values.Count > 0) _values.Sort(_sortComparer); IsSorted = true; } Debug.Assert(Count == _values.Count); if ((modifiers & (Int64)ModifierBits.DirtyBit) != 0) IsDirty = false; // Only clear the dirty bit if it was set } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] public List<T> Values { get { if (IsDirty) UpdateValues(); return _values; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] public List<T> SortedValues { get { // Directly check the modifiers bits to save one interlocked read Int64 modifiers = Interlocked.Read(ref _modifierBits); if ((modifiers & (Int64)ModifierBits.DirtyBit) != 0 || (modifiers & (Int64)ModifierBits.SortedBit) == 0) { UpdateAndSortValues(); } return _values; } } public List<T> GetValuesWithCustomSort(Comparison<T> comparison) { List<T> items = new List<T>(Count); if (!IsDirty) { // If the sorted values is up to date, get the keys from there. Debug.Assert(Count == _values.Count); items.AddRange(_values); } else { items.AddRange(_hash); } // Sort the items items.Sort(comparison); return items; } #region IEnumerable public List<T>.Enumerator GetEnumerator() { return Values.GetEnumerator(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region ICollection public void Clear() { ValidateReadOnly(); if (_hash.Count > 0) { IsDirty = true; _hash.Clear(); } } public bool Contains(T item) { return _hash.Contains(item); } public bool Remove(T item) { IsDirty = true; return _hash.Remove(item); } public int Count { get { return _hash.Count; } } internal void SetReadOnly(bool readOnly) { if (readOnly) Interlocked.Or(ref _modifierBits, (Int64)ModifierBits.ReadOnlyBit); else Interlocked.And(ref _modifierBits, ~(Int64)ModifierBits.ReadOnlyBit); } protected void ValidateReadOnly() { if (IsReadOnly) { throw new Error("Error: The list is readonly. Cannot modify the list during configuration."); } } public bool IsReadOnly { get { return (Interlocked.Read(ref _modifierBits) & (Int64)ModifierBits.ReadOnlyBit) != 0; } } #endregion public override string ToString() { StringBuilder builder = new StringBuilder(Count * 128); bool first = true; foreach (T value in _hash) { if (!first) builder.Append(','); else first = false; builder.Append(value.ToString()); } return builder.ToString(); } #region Internals internal void SetDirty() { IsDirty = true; } #endregion } }