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;
}
}
}