11// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22
33using System ;
4- using System . Collections . Generic ;
4+ using System . Collections . Concurrent ;
55using System . Diagnostics ;
66
77namespace Microsoft . VisualStudioTools . Project
88{
99 internal sealed class HierarchyIdMap
1010 {
11- private readonly List < WeakReference < HierarchyNode > > ids = new List < WeakReference < HierarchyNode > > ( ) ;
12- private readonly Stack < int > freedIds = new Stack < int > ( ) ;
11+ private readonly ConcurrentDictionary < uint , WeakReference < HierarchyNode > > nodes = new ConcurrentDictionary < uint , WeakReference < HierarchyNode > > ( ) ;
12+ private readonly ConcurrentStack < uint > freedIds = new ConcurrentStack < uint > ( ) ;
1313
14- private readonly object theLock = new object ( ) ;
14+ public readonly static HierarchyIdMap Instance = new HierarchyIdMap ( ) ;
15+
16+ private HierarchyIdMap ( ) { }
1517
1618 /// <summary>
1719 /// Must be called from the UI thread
1820 /// </summary>
1921 public uint Add ( HierarchyNode node )
2022 {
23+ VisualStudio . Shell . ThreadHelper . ThrowIfNotOnUIThread ( ) ;
24+ Debug . Assert ( node != null , "The node added here should never be null." ) ;
2125#if DEBUG
22- foreach ( var reference in this . ids )
26+ foreach ( var reference in this . nodes . Values )
2327 {
2428 if ( reference != null )
2529 {
@@ -30,24 +34,21 @@ public uint Add(HierarchyNode node)
3034 }
3135 }
3236#endif
33-
34- VisualStudio . Shell . ThreadHelper . ThrowIfNotOnUIThread ( ) ;
35-
36- lock ( this . theLock )
37+ if ( ! this . freedIds . TryPop ( out var idx ) )
3738 {
38- if ( this . freedIds . Count > 0 )
39- {
40- var i = this . freedIds . Pop ( ) ;
41- this . ids [ i ] = new WeakReference < HierarchyNode > ( node ) ;
42- return ( uint ) i + 1 ;
43- }
44- else
45- {
46- this . ids . Add ( new WeakReference < HierarchyNode > ( node ) ) ;
47- // ids are 1 based
48- return ( uint ) this . ids . Count ;
49- }
39+ idx = this . NextIndex ( ) ;
5040 }
41+
42+ var addSuccess = this . nodes . TryAdd ( idx , new WeakReference < HierarchyNode > ( node ) ) ;
43+ Debug . Assert ( addSuccess , "Failed to add a new item" ) ;
44+
45+ return idx ;
46+ }
47+
48+ private uint NextIndex ( )
49+ {
50+ // +1 since 0 is not a valid HierarchyId
51+ return ( uint ) this . nodes . Count + 1 ;
5152 }
5253
5354 /// <summary>
@@ -57,33 +58,17 @@ public void Remove(HierarchyNode node)
5758 {
5859 VisualStudio . Shell . ThreadHelper . ThrowIfNotOnUIThread ( ) ;
5960
60- if ( node == null )
61- {
62- throw new ArgumentNullException ( nameof ( node ) ) ;
63- }
61+ Debug . Assert ( node != null , "Called with null node" ) ;
6462
65- lock ( this . theLock )
66- {
67- var i = ( int ) node . ID - 1 ;
68- if ( 0 > i || i >= this . ids . Count )
69- {
70- throw new InvalidOperationException ( $ "Invalid id. { i } ") ;
71- }
63+ var idx = node . ID ;
7264
73- var weakRef = this . ids [ i ] ;
74- if ( weakRef == null )
75- {
76- throw new InvalidOperationException ( "Trying to retrieve a node before adding." ) ;
77- }
65+ var removeCheck = this . nodes . TryRemove ( idx , out var weakRef ) ;
7866
79- if ( weakRef . TryGetTarget ( out var found ) && ! object . ReferenceEquals ( node , found ) )
80- {
81- throw new InvalidOperationException ( "The node has the wrong id." ) ;
82- }
67+ Debug . Assert ( removeCheck , "How did we get an id, which we haven't seen before" ) ;
68+ Debug . Assert ( weakRef != null , "How did we insert a null value." ) ;
69+ Debug . Assert ( weakRef . TryGetTarget ( out var found ) && object . ReferenceEquals ( node , found ) , "The node has the wrong id, or was GC-ed before." ) ;
8370
84- this . ids [ i ] = null ;
85- this . freedIds . Push ( i ) ;
86- }
71+ this . freedIds . Push ( idx ) ;
8772 }
8873
8974 /// <summary>
@@ -93,15 +78,16 @@ public HierarchyNode this[uint itemId]
9378 {
9479 get
9580 {
96- var i = ( int ) itemId - 1 ;
97- if ( 0 <= i && i < this . ids . Count )
81+ VisualStudio . Shell . ThreadHelper . ThrowIfNotOnUIThread ( ) ;
82+
83+ var idx = itemId ;
84+ if ( this . nodes . TryGetValue ( idx , out var reference ) && reference != null && reference . TryGetTarget ( out var node ) )
9885 {
99- var reference = this . ids [ i ] ;
100- if ( reference != null && reference . TryGetTarget ( out var node ) )
101- {
102- return node ;
103- }
86+ Debug . Assert ( node != null ) ;
87+ return node ;
10488 }
89+
90+ // This is a valid return value, this gets called by VS after we deleted the item
10591 return null ;
10692 }
10793 }
0 commit comments