// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Linq;
using FlaxEngine.Collections;
namespace FlaxEditor.History
{
///
/// Controller for handling stack manipulations in history and reverse buffers.
///
public sealed class HistoryStack
{
private int _historyActionsLimit;
private readonly CircularBuffer _historyActions;
private readonly CircularBuffer _reverseActions;
///
/// Initializes a new instance of the class.
///
/// The history actions limit.
public HistoryStack(int historyActionsLimit = 1000)
{
if (historyActionsLimit < 1)
throw new ArgumentOutOfRangeException();
_historyActionsLimit = historyActionsLimit;
_historyActions = new CircularBuffer(_historyActionsLimit);
_reverseActions = new CircularBuffer(_historyActionsLimit);
_historyActions.OnItemOverflown += OnItemOverflown;
_reverseActions.OnItemOverflown += OnItemOverflown;
}
private void OnItemOverflown(object sender, CircularBuffer.ItemOverflownEventArgs e)
{
// Dispose item to prevent leaks
e.Item.Dispose();
}
///
/// Gets the history actions limit.
///
///
/// The history actions limit.
///
public int HistoryActionsLimit
{
get => _historyActionsLimit;
set
{
if (value < 1)
throw new ArgumentOutOfRangeException();
if (_historyActionsLimit == value)
return;
// Cache actions
var history = _historyActions.ToArray();
var reverse = _reverseActions.ToArray();
// Resize buffers
_historyActionsLimit = value;
_historyActions.Clear(_historyActionsLimit);
_reverseActions.Clear(_historyActionsLimit);
// Add actions back
for (int i = 0; i < _historyActionsLimit && i < history.Length; i++)
{
_historyActions.PushBack(history[i]);
}
for (int i = 0; i < _historyActionsLimit && i < reverse.Length; i++)
{
_reverseActions.PushBack(reverse[i]);
}
// Cleanup remaining actions
for (int i = _historyActionsLimit; i < history.Length; i++)
{
history[i].Dispose();
}
for (int i = _historyActionsLimit; i < reverse.Length; i++)
{
reverse[i].Dispose();
}
}
}
///
/// Gets the history count.
///
///
/// The history count.
///
public int HistoryCount => _historyActions.Count;
///
/// Gets the reverse count.
///
///
/// The reverse count.
///
public int ReverseCount => _reverseActions.Count;
///
/// Adds new history element at top of history stack, and drops reverse stack
///
/// Item to add
public void Push(IHistoryAction item)
{
_historyActions.PushFront(item);
ClearReverse();
}
///
/// Gets top-most item in history stack
///
/// Found element or null
public IHistoryAction PeekHistory()
{
return _historyActions.Count == 0 ? null : _historyActions[_historyActions.Count - 1];
}
///
/// Gets top-most item in reverse stack
///
/// Found element or null
public IHistoryAction PeekReverse()
{
return _reverseActions.Count == 0 ? null : _reverseActions[_reverseActions.Count - 1];
}
///
/// Gets top-most item in history stack, and removes it from history stack. Adds forgot element in reverse stack.
///
/// Found element or null
public IHistoryAction PopHistory()
{
var item = PeekHistory();
if (item == null)
return null;
_historyActions.PopFront();
_reverseActions.PushFront(item);
return item;
}
///
/// Gets top-most item in reverse stack, and removes it from reverse stack. Adds forgot element in history stack.
///
/// Found element or null
public IHistoryAction PopReverse()
{
var item = PeekReverse();
if (item == null)
return null;
_reverseActions.PopFront();
_historyActions.PushFront(item);
return item;
}
///
/// Gets element at given index from top of history stack, and adds all skipped elements to reverse stack
///
/// If skipElements is bigger, then amount of elements in history, returns null, clears history and pushes all to reverse stack
/// Amount of elements to skip from history stack
/// >Found element or null
public IHistoryAction TravelBack(int skipElements)
{
if (skipElements <= 0)
{
throw new ArgumentOutOfRangeException(nameof(skipElements), "argument cannot be smaller or equal to 0");
}
if (_historyActions.Count - skipElements <= 0)
{
foreach (var historyAction in _historyActions)
{
_reverseActions.PushFront(historyAction);
}
var result = _historyActions.Back();
ClearHistory();
return result;
}
// Iterate all but one elements to skip. Last element is handled exclusively
for (int i = 0; i < skipElements - 1; i++)
{
PopHistory();
}
return PopHistory();
}
///
/// Gets element at given index from top of reverse stack, and adds all skipped elements to history stack
///
/// If skipElements is bigger, then amount of elements in reverse, returns null, clears reverse and pushes all to history stack
/// Amount of elements to skip from reverse stack
/// >Found element or null
public IHistoryAction TravelReverse(int skipElements)
{
if (skipElements <= 0)
{
throw new ArgumentOutOfRangeException(nameof(skipElements), "skipElement cannot be smaller or equal to 0");
}
if (_reverseActions.Count - skipElements <= 0)
{
foreach (var reverseAction in _reverseActions.Reverse())
{
_historyActions.PushFront(reverseAction);
}
ClearReverse();
return PeekHistory();
}
// iterate all but one elements to skip. Last element is handled exclusively
for (int i = 0; i < skipElements - 1; i++)
{
PopReverse();
}
return PopReverse();
}
///
/// Clears whole history (back and front).
///
public void Clear()
{
ClearHistory();
ClearReverse();
}
private void ClearHistory()
{
if (_historyActions.Count > 0)
{
var actions = _historyActions.ToArray();
for (int i = 0; i < actions.Length; i++)
actions[i].Dispose();
_historyActions.Clear();
}
}
private void ClearReverse()
{
if (_reverseActions.Count > 0)
{
var actions = _reverseActions.ToArray();
for (int i = 0; i < actions.Length; i++)
actions[i].Dispose();
_reverseActions.Clear();
}
}
}
}