// 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();
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}