using System.Collections; using System.Runtime; using System.Runtime.CompilerServices; namespace XFESystem.Browser; /// Page-owned ephemerons. Buckets contain no strong key references; /// a value that refers back to its key cannot keep a detached node alive. /// Pruning and release explicitly free native handles without finalizers. internal sealed class DependentIdentityMap : IEnumerable> where TKey : class where TValue : class { private sealed class Entry(TKey key,TValue value) { internal DependentHandle Handle = new(key,value); } private readonly Dictionary> buckets=[]; private readonly List emptyBuckets=[]; private int count; public long HandlesCreated {get;private set;} public long HandlesDisposed {get;private set;} public int Count {get{Prune();return count;}} public IEnumerable Keys => this.Select(pair=>pair.Key); public TValue this[TKey key] => TryGetValue(key,out var value)?value:throw new KeyNotFoundException(); public bool ContainsKey(TKey key)=>TryGetValue(key,out _); public bool TryGetValue(TKey key,out TValue value) { if(buckets.TryGetValue(RuntimeHelpers.GetHashCode(key),out var bucket)) foreach(var entry in bucket) { var pair=entry.Handle.TargetAndDependent; if(ReferenceEquals(pair.Target,key)&&pair.Dependent is TValue found){value=found;return true;} } value=null!;return false; } public void Add(TKey key,TValue value) { ArgumentNullException.ThrowIfNull(key);ArgumentNullException.ThrowIfNull(value); int hash=RuntimeHelpers.GetHashCode(key); if(!buckets.TryGetValue(hash,out var bucket))buckets.Add(hash,bucket=[]); for(int index=bucket.Count-1;index>=0;index--) { var entry=bucket[index];var pair=entry.Handle.TargetAndDependent; if(pair.Target is null){DisposeEntry(entry);bucket.RemoveAt(index);continue;} if(ReferenceEquals(pair.Target,key))throw new ArgumentException("The identity key already exists.",nameof(key)); } bucket.Add(new(key,value));count++;HandlesCreated++; } public bool Remove(TKey key)=>Remove(key,out _); public bool Remove(TKey key,out TValue value) { int hash=RuntimeHelpers.GetHashCode(key); if(buckets.TryGetValue(hash,out var bucket)) for(int index=0;index=0;index--) if(bucket[index].Handle.Target is null){DisposeEntry(bucket[index]);bucket.RemoveAt(index);} if(bucket.Count==0)emptyBuckets.Add(item.Key); } foreach(int hash in emptyBuckets)buckets.Remove(hash); emptyBuckets.Clear(); } public IEnumerator> GetEnumerator() { foreach(var bucket in buckets.Values)foreach(var entry in bucket) { var pair=entry.Handle.TargetAndDependent; if(pair.Target is TKey key&&pair.Dependent is TValue value)yield return new(key,value); } } IEnumerator IEnumerable.GetEnumerator()=>GetEnumerator(); }