using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; namespace UnityAtoms { /// /// Generic base class for Value Lists. Inherits from `BaseAtomList` and `IList<T>`. /// /// The list item type. /// Event of type `AtomEvent<T>`. [EditorIcon("atom-icon-piglet")] public abstract class AtomValueList : BaseAtomValueList, IList where E : AtomEvent { /// /// Event for when something is added to the list. /// public E Added; /// /// Event for when something is removed from the list. /// public E Removed; /// /// Get the count of the list. /// public int Count => list.Count; /// /// Is the list read only? /// public bool IsReadOnly => false; /// /// Actual `List<T>`. /// /// The list item type. /// The actual `List<T>`. [SerializeField] protected List list = new List(); private List _initial; #if UNITY_EDITOR /// /// Set of all AtomVariable instances in editor. /// private static HashSet> _instances = new HashSet>(); #endif protected override void OnEnable() { base.OnEnable(); #if UNITY_EDITOR _instances.Add(this); EditorApplication.playModeStateChanged -= HandlePlayModeStateChange; EditorApplication.playModeStateChanged += HandlePlayModeStateChange; #endif } private void OnDisable() { // NOTE: This will not be called when deleting the Atom from the editor. // Therefore, there might still be null instances, but even though not ideal, // it should not cause any problems. // More info: https://issuetracker.unity3d.com/issues/ondisable-and-ondestroy-methods-are-not-called-when-a-scriptableobject-is-deleted-manually-in-project-window #if UNITY_EDITOR _instances.Remove(this); #endif } #if UNITY_EDITOR private static void HandlePlayModeStateChange(PlayModeStateChange state) { if (state == PlayModeStateChange.ExitingEditMode) // BEFORE any GO is initialized: { foreach (var instance in _instances) { if (instance._startCleared) instance.list.Clear(); instance._initial = instance.list.ToList(); } } else if (state == PlayModeStateChange.EnteredEditMode) // AFTER Playmode stopped { foreach (var instance in _instances) { instance.list = instance._initial; } } } #endif /// /// Add an item to the list. /// /// The item to add. public void Add(T item) { list.Add(item); if (null != Added) { Added.Raise(item); } } /// /// Remove an item from the list. /// /// The item to remove. /// True if it was removed, otherwise false.. public bool Remove(T item) { var removed = list.Remove(item); if (!removed) return false; if (null != Removed) { Removed.Raise(item); } return true; } /// /// Removes an item from the list. /// This is a wrapper for `bool Remove(T item)`, useful for usage in UnityEvents. /// /// The item to remove. public void RemoveItem(T item) => Remove(item); /// /// Does the list contain the item provided? /// /// The item to check if it is contained in the list. /// `true` if the item exists in the list, otherwise `false`. public bool Contains(T item) { return list.Contains(item); } /// /// Get item at index. /// /// The index. /// The item if it exists. public T Get(int i) { return list[i]; } /// /// The actual `List<T>` as a property. /// /// Get or set the value of the list. public List List { get { return list; } set { list = value; } } /// /// Indexer of the list. /// /// Get or set an item via index in the list. public T this[int index] { get { return list[index]; } set { list[index] = value; } } /// /// Copies the entire List to a compatible one-dimensional array, starting at the specified index of the target array. /// /// The one-dimensional Array that is the destination of the elements copied from List. The Array must have zero-based indexing. /// The zero-based index in `array` at which copying begins. public void CopyTo(T[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator(); /// /// Get an `IEnumerator<T>` of the list. /// /// An `IEnumerator<T>` public IEnumerator GetEnumerator() => list.GetEnumerator(); /// /// Returns the index of the specified item. /// /// The item to search for. /// The zero-based index of the first occurrence of `item`. If not found it returns -1. public int IndexOf(T item) => list.IndexOf(item); /// /// Remove an item at provided index. /// /// The index to remove item at. public void RemoveAt(int index) { var item = list[index]; list.RemoveAt(index); if (null != Removed) { Removed.Raise(item); } } /// /// Insert item at index. /// /// Index to insert item at. /// Item to insert. public void Insert(int index, T item) { list.Insert(index, item); if (Added != null) { Added.Raise(item); } } #region Observable /// /// Make the add event into an `IObservable<T>`. Makes Value List's add Event compatible with for example UniRx. /// /// The add Event as an `IObservable<T>`. public IObservable ObserveAdd() { if (Added == null) { throw new Exception("You must assign an Added event in order to observe when adding to the value list."); } return new ObservableEvent(Added.Register, Added.Unregister); } /// /// Make the remove event into an `IObservable<T>`. Makes Value List's remove Event compatible with for example UniRx. /// /// The remove Event as an `IObservable<T>`. public IObservable ObserveRemove() { if (Removed == null) { throw new Exception("You must assign a Removed event in order to observe when removing from the value list."); } return new ObservableEvent(Removed.Register, Removed.Unregister); } /// /// Make the clear event into an `IObservable<Void>`. Makes Value List's clear Event compatible with for example UniRx. /// /// The clear Event as an `IObservable<Void>`. public IObservable ObserveClear() { if (Cleared == null) { throw new Exception("You must assign a Cleared event in order to observe when clearing the value list."); } return new ObservableVoidEvent(Cleared.Register, Cleared.Unregister); } #endregion // Observable protected override IList IList { get => List; } } }