// Copyright (c) 2023 bradson // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Diagnostics.CodeAnalysis; using System.Linq; namespace PerformanceFish.Cache; public class ByIndex : IList, ICollection { private static ByIndex _get = Utility.AddNew>(); [ThreadStatic] private static ByIndex? _getThreadStatic; public static ByIndex Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNew>(); } public static ByIndex GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } static ByIndex() => Utility.All.Add(_get); protected T_out?[] _items = Array.Empty(); protected object? _syncRoot; public T_out? this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { ResizeIfNecessary(index); return _items[index]; } set { ResizeIfNecessary(index); _items[index] = value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] [return: MaybeNull] public ref T_out GetReference(int index) { ResizeIfNecessary(index); return ref _items[index]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(int index, out T_out? value) { value = this[index]; return !value!.Equals(default!); } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void ResizeIfNecessary(int checkedIndex) { if (checkedIndex >= _items.Length) ResizeFor(checkedIndex); } protected void ResizeFor(int index) { Array.Resize(ref _items, Mathf.NextPowerOfTwo(index + 1)); ResizeIfNecessary(index); // just in case } public int Count => _items.Length; public bool IsReadOnly => false; object ICollection.SyncRoot => _syncRoot ??= Interlocked.CompareExchange(ref _syncRoot!, new(), null!); bool ICollection.IsSynchronized => false; [DoesNotReturn] public void Add(T_out? item) => throw new NotSupportedException(); public void Clear() { if (_items.Length > 0) Array.Clear(_items, 0, _items.Length); } public bool Contains(T_out? item) => Array.IndexOf(_items, item) >= 0; public void CopyTo(T_out?[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator() => _items.AsEnumerable().GetEnumerator(); public int IndexOf(T_out? item) => Array.IndexOf(_items, item); public void Insert(int index, T_out? item) => this[index] = item; [DoesNotReturn] public bool Remove(T_out? item) => throw new NotSupportedException(); public void RemoveAt(int index) { if (index < Count) _items[index] = default; } IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); void ICollection.CopyTo(Array array, int index) => _items.CopyTo(array, index); } public class ByIndex : ByIndex where T_in : notnull { private static ByIndex _get = Utility.AddNew>(); [ThreadStatic] private static ByIndex? _getThreadStatic; public new static ByIndex Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNew>(); } public new static ByIndex GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } static ByIndex() => Utility.All.Add(_get); public unsafe T_out? this[T_in key] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this[FunctionPointers.IndexGetter.Default(key)]; set => this[FunctionPointers.IndexGetter.Default(key)] = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(T_in key, [NotNullWhen(true)] out T_out? value) { value = this[key]; return !value!.Equals(default!); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [return: MaybeNull] public unsafe ref T_out GetReference(T_in key) => ref GetReference(FunctionPointers.IndexGetter.Default(key))!; } /// /// Seems slower than the dictionary based Cache.ByInt. /// public class ByIndex : ByIndex> where T_first : notnull where T_second : notnull { private static ByIndex _get = Utility.AddNew>(); [ThreadStatic] private static ByIndex? _getThreadStatic; public new static ByIndex Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNew>(); } public new static ByIndex GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } static ByIndex() => Utility.All.Add(_get); public T_out? this[T_first first, T_second second] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => GetInner(first)[second]; set => GetInner(first)[second] = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe ByIndex GetInner(T_first first) { var firstIndex = FunctionPointers.IndexGetter.Default(first); ResizeIfNecessary(firstIndex); ref var inner = ref _items[firstIndex]; inner ??= new(); return inner; } [MethodImpl(MethodImplOptions.AggressiveInlining)] [return: MaybeNull] public ref T_out GetReference(T_first first, T_second second) => ref GetInner(first).GetReference(second)!; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(T_first first, T_second second, [NotNullWhen(true)] out T_out? value) { value = this[first, second]; return !value!.Equals(default!); } }