#nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; using JetBrains.Annotations; namespace Robust.Shared.Utility { /// /// An immutable dictionary of mutable and immutable sets for use as an index of unique values related to another collection. /// Imitates the behavior of a read-focused index in a RDBMS. /// /// /// Use when the index's keys don't change that rapidly or when fast lookup is preferred over creation time. /// It is not intended to explicitly construct this index before use. /// Do not refer to a by it's interface. /// See for details. /// /// The type of key. /// The type of value. /// /// [PublicAPI] public struct UniqueIndex : IUniqueIndex where TKey : notnull { private ImmutableDictionary>? _index; /// public int KeyCount => _index?.Count ?? 0; /// public bool Add(TKey key, TValue value) { ISet? set; if (_index is null) { set = new HashSet {value}; _index = ImmutableDictionary.CreateRange(new[] {new KeyValuePair>(key, set)}); return true; } if (_index.TryGetValue(key, out set)) { return set.Add(value); } _index = _index.Add(key, new HashSet {value}); return true; } /// public int AddRange(TKey key, IEnumerable values) { ISet? set; if (_index is null) { set = new HashSet(values); _index = ImmutableDictionary.CreateRange(new[] {new KeyValuePair>(key, set)}); return set.Count; } if (_index.TryGetValue(key, out set)) { var c = set.Count; set.UnionWith(values); return set.Count - c; } _index = _index.Add(key, set = new HashSet(values)); return set.Count; } /// public bool Remove(TKey key) { if (_index == null) { return false; } var newIndex = _index.SetItem(key, new HashSet()); if (_index != newIndex) { return false; } _index = newIndex; return true; } /// public bool Remove(TKey key, TValue value) { // ReSharper disable once InvertIf if (_index == null) { return false; } return _index.TryGetValue(key, out var set) && set.Remove(value); } /// public int RemoveRange(TKey key, IEnumerable values) { if (_index == null) { return 0; } if (!_index.TryGetValue(key, out var set)) { return 0; } var c = set.Count; set.ExceptWith(set); return c - set.Count; } /// public bool Replace(TKey key, TValue oldValue, TValue newValue) { if (_index == null) { return false; } if (!_index.TryGetValue(key, out var set)) { return false; } return set.Remove(oldValue) && set.Add(newValue); } /// public void Touch(TKey key) { _index ??= ImmutableDictionary>.Empty; if (_index.ContainsKey(key)) return; _index = _index.Add(key, new HashSet()); } /// public bool Freeze(TKey key) { if (_index is null) { return false; } if (!_index.TryGetValue(key, out var set) || set is ImmutableHashSet) { return false; } _index = _index.SetItem(key, ImmutableHashSet.CreateRange(set)); return true; } /// public void Initialize(IEnumerable keys) => Initialize(keys.Select(k => new KeyValuePair>(k, new HashSet()))); /// public void Initialize(IEnumerable>> index) { if (_index != null) throw new InvalidOperationException("Already initialized."); _index = ImmutableDictionary.CreateRange(index); } public ISet this[TKey key] { get { ISet? set; if (_index is null) { _index = ImmutableDictionary>.Empty; } else { if (_index.TryGetValue(key, out set)) { return set; } } _index = _index.Add(key, set = new HashSet()); return set; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public IEnumerator>> GetEnumerator() { if (_index != null) { return _index.GetEnumerator(); } return Enumerable.Empty>>().GetEnumerator(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }