Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions src/Build/BackEnd/Components/RequestBuilder/Lookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ private void MergeScopeIntoLastScope()
{
foreach (KeyValuePair<string, List<ProjectItemInstance>> kvp in PrimaryRemoveTable)
{
_baseItems.RemoveItems(kvp.Value);
_baseItems.RemoveItemsOfType(kvp.Key, kvp.Value);
}
}

Expand Down Expand Up @@ -974,14 +974,11 @@ private void ApplyModificationsToTable(IItemDictionary<ProjectItemInstance> tabl
ICollection<ProjectItemInstance> existing = table[itemType];
if (existing != null)
{
foreach (var kvPair in modify)
foreach (ProjectItemInstance item in existing)
{
if (table.Contains(kvPair.Key))
if (modify.TryGetValue(item, out MetadataModifications modificationsToApply))
{
var itemToModify = kvPair.Key;
var modificationsToApply = kvPair.Value;

ApplyMetadataModificationsToItem(modificationsToApply, itemToModify);
ApplyMetadataModificationsToItem(modificationsToApply, item);
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions src/Build/Collections/IItemDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ internal interface IItemDictionary<T> : IEnumerable<T>, IItemProvider<T>
/// </param>
void EnumerateItemsPerType(Action<string, IEnumerable<T>> itemTypeCallback);

/// <summary>
/// Whether the provided item is in this table or not.
/// </summary>
bool Contains(T projectItem);

/// <summary>
/// Add a new item to the collection, at the
/// end of the list of other items with its key.
Expand Down Expand Up @@ -92,7 +87,8 @@ internal interface IItemDictionary<T> : IEnumerable<T>, IItemProvider<T>
/// <summary>
/// Remove the set of items specified from this dictionary
/// </summary>
/// <param name="itemType">The item type for all removes.</param>
/// <param name="other">An enumerator over the items to remove.</param>
void RemoveItems(IEnumerable<T> other);
void RemoveItemsOfType(string itemType, IEnumerable<T> other);
}
}
133 changes: 90 additions & 43 deletions src/Build/Collections/ItemDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,15 @@ internal sealed class ItemDictionary<T> : ICollection<T>, IItemDictionary<T>
/// Dictionary of item lists used as a backing store.
/// This collection provides quick access to the ordered set of items of a particular type.
/// </summary>
private readonly Dictionary<string, LinkedList<T>> _itemLists;

/// <summary>
/// Dictionary of items in the collection, to speed up Contains,
/// Remove, and Replace. For those operations, we look up here,
/// then modify the other dictionary to match.
/// </summary>
private readonly Dictionary<T, LinkedListNode<T>> _nodes;
private readonly Dictionary<string, List<T>> _itemLists;

/// <summary>
/// Constructor for an empty collection.
/// </summary>
public ItemDictionary()
{
// Tracing.Record("new item dictionary");
_itemLists = new Dictionary<string, LinkedList<T>>(MSBuildNameIgnoreCaseComparer.Default);
_nodes = new Dictionary<T, LinkedListNode<T>>();
_itemLists = new Dictionary<string, List<T>>(MSBuildNameIgnoreCaseComparer.Default);
}

/// <summary>
Expand All @@ -64,8 +56,7 @@ public ItemDictionary()
public ItemDictionary(int initialItemTypesCapacity, int initialItemsCapacity = 0)
{
// Tracing.Record("new item dictionary");
_itemLists = new Dictionary<string, LinkedList<T>>(initialItemTypesCapacity, MSBuildNameIgnoreCaseComparer.Default);
_nodes = new Dictionary<T, LinkedListNode<T>>(initialItemsCapacity);
_itemLists = new Dictionary<string, List<T>>(initialItemTypesCapacity, MSBuildNameIgnoreCaseComparer.Default);
}

/// <summary>
Expand All @@ -74,15 +65,29 @@ public ItemDictionary(int initialItemTypesCapacity, int initialItemsCapacity = 0
public ItemDictionary(IEnumerable<T> items)
{
// Tracing.Record("new item dictionary");
_itemLists = new Dictionary<string, LinkedList<T>>(MSBuildNameIgnoreCaseComparer.Default);
_nodes = new Dictionary<T, LinkedListNode<T>>();
_itemLists = new Dictionary<string, List<T>>(MSBuildNameIgnoreCaseComparer.Default);
ImportItems(items);
}

/// <summary>
/// Number of items in total, for debugging purposes.
/// </summary>
public int Count => _nodes.Count;
public int Count
{
get
{
int count = 0;
lock (_itemLists)
{
foreach (List<T> list in _itemLists.Values)
{
count += list.Count;
}
}

return count;
}
}

/// <summary>
/// Get the item types that have at least one item in this collection
Expand Down Expand Up @@ -117,7 +122,7 @@ public ICollection<T> this[string itemtype]
{
get
{
LinkedList<T> list;
List<T> list;
lock (_itemLists)
{
if (!_itemLists.TryGetValue(itemtype, out list))
Expand All @@ -137,13 +142,12 @@ public void Clear()
{
lock (_itemLists)
{
foreach (ICollection<T> list in _itemLists.Values)
foreach (List<T> list in _itemLists.Values)
{
list.Clear();
}

_itemLists.Clear();
_nodes.Clear();
}
}

Expand Down Expand Up @@ -227,11 +231,19 @@ public ICollection<T> GetItems(string itemType)
/// <summary>
/// Whether the provided item is in this table or not.
/// </summary>
public bool Contains(T projectItem)
bool ICollection<T>.Contains(T projectItem)
{
lock (_itemLists)
{
return _nodes.ContainsKey(projectItem);
foreach (List<T> list in _itemLists.Values)
{
if (list.Contains(projectItem))
{
return true;
}
}

return false;
}
}

Expand Down Expand Up @@ -259,23 +271,31 @@ public bool Remove(T projectItem)
{
lock (_itemLists)
{
if (!_nodes.TryGetValue(projectItem, out LinkedListNode<T> node))
if (!_itemLists.TryGetValue(projectItem.Key, out List<T> list))
{
return false;
}

LinkedList<T> list = node.List;
list.Remove(node);
_nodes.Remove(projectItem);

// Save memory
if (list.Count == 0)
// Searching for a single object - just compare the reference pointer.
for (int i = 0; i < list.Count; i++)
{
_itemLists.Remove(projectItem.Key);
}
T candidateItem = list[i];
if (ReferenceEquals(candidateItem, projectItem))
{
list.RemoveAt(i);

return true;
// Save memory if the item type is now empty.
if (list.Count == 0)
{
_itemLists.Remove(projectItem.Key);
}

return true;
}
}
}

return false;
}

/// <summary>
Expand Down Expand Up @@ -303,46 +323,73 @@ public void ImportItemsOfType(string itemType, IEnumerable<T> items)
{
lock (_itemLists)
{
if (!_itemLists.TryGetValue(itemType, out LinkedList<T> list))
if (!_itemLists.TryGetValue(itemType, out List<T> list))
{
list = new LinkedList<T>();
list = new List<T>();
_itemLists[itemType] = list;
}

foreach (T item in items)
int count = list.Count;
list.AddRange(items);

for (int i = count; i < list.Count; i++)
{
#if DEBUG
// Debug only: hot code path
ErrorUtilities.VerifyThrow(String.Equals(itemType, item.Key, StringComparison.OrdinalIgnoreCase), "Item type mismatch");
ErrorUtilities.VerifyThrow(String.Equals(itemType, list[i].Key, StringComparison.OrdinalIgnoreCase), "Item type mismatch");
#endif
LinkedListNode<T> node = list.AddLast(item);
_nodes.Add(item, node);
}
}
}

/// <summary>
/// Remove the set of items specified from this dictionary
/// </summary>
/// <param name="itemType">The item type for all removes.</param>
/// <param name="other">An enumerator over the items to remove.</param>
public void RemoveItems(IEnumerable<T> other)
public void RemoveItemsOfType(string itemType, IEnumerable<T> other)
{
foreach (T item in other)
lock (_itemLists)
{
Remove(item);
if (!_itemLists.TryGetValue(itemType, out List<T> list))
{
return;
}

// Since we'll need to search and remove an unknown number of items, we'll build up a new list of items to
// keep, using the incoming enumerable as a set, and swap out the result at the end.
// This minimizes the upper bound of ops and allocations here.
List<T> listWithRemoves = new(list.Count);
HashSet<T> itemsToRemove = new(other);
foreach (T item in list)
{
if (!itemsToRemove.Contains(item))
{
listWithRemoves.Add(item);
}
}

if (listWithRemoves.Count > 0)
{
_itemLists[itemType] = listWithRemoves;
}
else
{
// If the clone is empty, remove the item type from the dictionary
_itemLists.Remove(itemType);
}
}
}

private void AddProjectItem(T projectItem)
{
if (!_itemLists.TryGetValue(projectItem.Key, out LinkedList<T> list))
if (!_itemLists.TryGetValue(projectItem.Key, out List<T> list))
{
list = new LinkedList<T>();
list = new List<T>();
_itemLists[projectItem.Key] = list;
}

LinkedListNode<T> node = list.AddLast(projectItem);
_nodes.Add(projectItem, node);
list.Add(projectItem);
}

public void CopyTo(T[] array, int arrayIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ internal sealed class ImmutableItemDictionary<TCached, T> : IItemDictionary<T>
private readonly IDictionary<string, ICollection<TCached>> _itemsByType;
private readonly ICollection<TCached> _allCachedItems;
private readonly Func<TCached, T?> _getInstance;
private readonly Func<T, string?> _getItemType;

public ImmutableItemDictionary(
ICollection<TCached> allItems,
Expand All @@ -37,7 +36,6 @@ public ImmutableItemDictionary(
_allCachedItems = allItems;
_itemsByType = itemsByType ?? throw new ArgumentNullException(nameof(itemsByType));
_getInstance = getInstance;
_getItemType = getItemType;
}

/// <inheritdoc />
Expand Down Expand Up @@ -66,24 +64,6 @@ public ICollection<T> this[string itemType]
/// <inheritdoc />
public void Clear() => throw new NotSupportedException();

/// <inheritdoc />
public bool Contains(T projectItem)
{
if (projectItem == null)
{
return false;
}

string? itemType = _getItemType(projectItem);
if (itemType == null)
{
return false;
}

ICollection<T> items = GetItems(itemType);
return items.Contains(projectItem);
}

/// <inheritdoc />
public void EnumerateItemsPerType(Action<string, IEnumerable<T>> itemTypeCallback)
{
Expand Down Expand Up @@ -159,7 +139,7 @@ public ICollection<T> GetItems(string itemType)
public bool Remove(T projectItem) => throw new NotSupportedException();

/// <inheritdoc />
public void RemoveItems(IEnumerable<T> other) => throw new NotSupportedException();
public void RemoveItemsOfType(string itemType, IEnumerable<T> other) => throw new NotSupportedException();

private sealed class ListConverter : ICollection<T>
{
Expand Down