namespace Zinnia.Data.Collection.Stack { using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using Zinnia.Extension; /// /// A collection of events to emit when a new is added or removed from the stack. /// public abstract class ObservableStackElementEvents where TEvent : UnityEvent, new() { /// /// Emitted when a new is added to the end of the stack. /// public TEvent Pushed = new TEvent(); /// /// Emitted when the is removed from the end of the stack. /// public TEvent Popped = new TEvent(); /// /// Emitted when the is removed from the stack due to a lower down in the stack being removed. /// public TEvent ForcePopped = new TEvent(); /// /// Emitted when the of the stack is restored to being at the top after elements above it being popped off. /// public TEvent Restored = new TEvent(); } /// /// Allows observing changes to a stack. /// /// The type of the elements in the stack. /// The events to emit per element. /// The type to use. public abstract class ObservableStack : MonoBehaviour where TElementEvents : ObservableStackElementEvents where TEvent : UnityEvent, new() { [Tooltip("The events to emit for the TElement that is added to the same index within the stack.")] [SerializeField] private List elementEvents = new List(); /// /// The events to emit for the that is added to the same index within the stack. /// public List ElementEvents { get { return elementEvents; } set { elementEvents = value; } } /// /// The current index the events to emit is at in relation to the stack. /// public int EventIndex { get; protected set; } /// /// A representation of the stack. /// public List Stack { get; protected set; } = new List(); /// /// Determines whether to abort a running pop process. /// protected bool abortPopProcess; /// /// Push an element onto the stack and emit the related events. /// /// The element to push onto the stack and to become the payload of the related event. public virtual void Push(TElement element) { if (!this.IsValidState() || EventIndex >= ElementEvents.Count || Stack.Contains(element)) { return; } Stack.Add(element); EventIndex++; ElementEvents[Stack.Count - 1].Pushed?.Invoke(element); } /// /// Pops the last element off the stack and emit the related events. /// public virtual void Pop() { if (!this.IsValidState()) { return; } if (Stack.Count > 0) { PopAt(Stack[Stack.Count - 1]); } } /// /// Pops from the stack at the given stack index. /// /// The index at which to pop the stack at. public virtual void PopAt(int index) { if (!this.IsValidState()) { return; } if (index < Stack.Count && index <= EventIndex) { PopAt(Stack[index]); } } /// /// Pops the given element from the stack and subsequently remove any elements that are above the given element in the stack and emit the related events. /// /// The element to pop from the stack. public virtual void PopAt(TElement element) { if (!this.IsValidState()) { return; } int elementIndex = Stack.IndexOf(element); if (elementIndex < 0) { return; } for (int currentIndex = ElementEvents.Count - 1; currentIndex >= elementIndex; currentIndex--) { if (abortPopProcess) { abortPopProcess = false; return; } if (elementIndex < Stack.Count && currentIndex < Stack.Count) { TElement currentElement = Stack[currentIndex]; Stack.Remove(currentElement); EventIndex = elementIndex; if (currentIndex == elementIndex) { ElementEvents[currentIndex].Popped?.Invoke(currentElement); } else { ElementEvents[currentIndex].ForcePopped?.Invoke(currentElement); } } } if (EventIndex < 0) { EventIndex = 0; } else if (EventIndex > 0) { ElementEvents[EventIndex - 1].Restored?.Invoke(Stack[EventIndex - 1]); } } /// /// Aborts the current stack pop process to prevent any further elements from being popped off the stack. /// public virtual void AbortPop() { if (!this.IsValidState()) { return; } abortPopProcess = true; } } }