// Animancer // Copyright 2020 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Animancer { /// /// An object with a so it can be used in a . /// public interface IKeyHolder { /// The which stores the list index of this object. Key Key { get; } } /// /// Stores the index of an object in a to allow it to be efficiently removed. /// public class Key : IKeyHolder { /************************************************************************************************************************/ private int _Index = -1; /// Returns location of this object in the list (or -1 if it is not currently in a keyed list). public static int IndexOf(Key key) { return key._Index; } /// Indicates whether the specified object is currently in a keyed list. public static bool IsInList(Key key) { return key._Index != -1; } /************************************************************************************************************************/ /// A is its own . Key IKeyHolder.Key { get { return this; } } /************************************************************************************************************************/ /// /// A which can remove items without needing to search through the entire collection. /// Does not allow nulls to be added. /// /// /// /// To use an object in a Keyed List, it must either inherit from or implement /// like so: /// /// class MyClass : IKeyHolder /// { /// private readonly Key Key = new Key(); /// Key IKeyHolder.Key { get { return Key; } } /// } /// /// Note that the Key field can be made public if desired. /// /// /// /// This class is nested inside so it can modify the private without /// exposing that capability to anything else. /// public sealed class KeyedList : IList where T : class, IKeyHolder { /************************************************************************************************************************/ private const string SingleUse = "Each item can only be used in one Keyed List at a time.", NotFound = "The specified item does not exist in this list."; /************************************************************************************************************************/ private readonly List Items; /************************************************************************************************************************/ /// Creates a new using the default constructor. public KeyedList() { Items = new List(); } /// Creates a new with the specified initial `capacity`. public KeyedList(int capacity) { Items = new List(capacity); } // No copy constructor because the keys will not work if they are used in multiple lists at once. /************************************************************************************************************************/ /// The number of items currently in the list. public int Count { get { return Items.Count; } } /// The number of items that this list can contain before resizing is required. public int Capacity { get { return Items.Capacity; } set { Items.Capacity = value; } } /************************************************************************************************************************/ /// Gets or sets the item at the specified `index`. /// Thrown by the setter if the `value` was already in a keyed list. public T this[int index] { get { return Items[index]; } set { var key = value.Key; if (key._Index != -1) throw new ArgumentException(SingleUse); var item = Items[index]; item.Key._Index = -1; key._Index = index; Items[index] = value; } } /************************************************************************************************************************/ /// Adds the `item` to the end of this list. /// Thrown if the `item` was already in a keyed list. public void Add(T item) { var key = item.Key; if (key._Index != -1) throw new ArgumentException(SingleUse); key._Index = Items.Count; Items.Add(item); } /// Adds the `item` to the end of this list if it wasn't already in one. public void AddNew(T item) { if (!IsInList(item.Key)) Add(item); } /************************************************************************************************************************/ /// Removes the item at the specified `index`. public void RemoveAt(int index) { Items[index].Key._Index = -1; Items.RemoveAt(index); } /// /// Removes the item at the specified `index` by swapping the last item in this list into its place. /// /// This does not maintain the order of items, but is more efficient than because /// it avoids the need to move every item after the removed one down one place. /// public void RemoveAtSwap(int index) { Items[index].Key._Index = -1; var lastIndex = Items.Count - 1; if (lastIndex > index) { var lastItem = Items[lastIndex]; lastItem.Key._Index = index; Items[index] = lastItem; } Items.RemoveAt(lastIndex); } /************************************************************************************************************************/ /// Removes the `item` from this list. public bool Remove(T item) { var key = item.Key; var index = key._Index; if (index < 0) return false; Debug.Assert(Items[index] == item, NotFound); key._Index = -1; Items.RemoveAt(index); return true; } /************************************************************************************************************************/ /// /// Removes the `item` by swapping the last item in this list into its place. /// /// This does not maintain the order of items, but is more efficient than because /// it avoids the need to move every item after the removed one down one place. /// public bool RemoveSwap(T item) { var key = item.Key; var index = key._Index; if (index < 0) return false; Debug.Assert(Items[index] == item, NotFound); key._Index = -1; var lastIndex = Items.Count - 1; if (lastIndex > index) { var lastItem = Items[lastIndex]; lastItem.Key._Index = index; Items[index] = lastItem; } Items.RemoveAt(lastIndex); return true; } /************************************************************************************************************************/ /// Removes all items from this list. public void Clear() { for (int i = Items.Count - 1; i >= 0; i--) { Items[i].Key._Index = -1; } Items.Clear(); } /************************************************************************************************************************/ /// Indicates whether the `item` is currently in this list. public bool Contains(T item) { if (item == null) return false; var index = item.Key._Index; return index >= 0 && index < Items.Count && Items[index] == item; } /************************************************************************************************************************/ /// Returns the index of the `item` in this list or -1 if it is not in this list. public int IndexOf(T item) { if (item == null) return -1; var index = item.Key._Index; if (index >= 0 && index < Items.Count && Items[index] == item) return index; else return -1; } /************************************************************************************************************************/ /// Adds the `item` to this list at the specified `index`. public void Insert(int index, T item) { for (int i = index; i < Items.Count; i++) Items[i].Key._Index++; item.Key._Index = index; Items.Insert(index, item); } /************************************************************************************************************************/ /// Copies all the items from this list into the `array`, starting at the specified `arrayIndex`. public void CopyTo(T[] array, int arrayIndex) { Items.CopyTo(array, arrayIndex); } /// Returns false. bool ICollection.IsReadOnly { get { return false; } } /// Returns an enumerator that iterates through this list. public IEnumerator GetEnumerator() { return Items.GetEnumerator(); } /// Returns an enumerator that iterates through this list. IEnumerator IEnumerable.GetEnumerator() { return Items.GetEnumerator(); } /************************************************************************************************************************/ } /************************************************************************************************************************/ } }