From dddeab8e12a7cc062c9c483a09f236c8f9bad4a5 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Tue, 30 Jun 2020 14:25:02 +1000 Subject: [PATCH] Refactor UndoHistory --- .../tools/RamSearchEngine/RamSearchEngine.cs | 8 +- src/BizHawk.Common/UndoHistory.cs | 102 +++++++----------- 2 files changed, 45 insertions(+), 65 deletions(-) diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs index 42e2adf824..f8093331eb 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs @@ -16,7 +16,7 @@ namespace BizHawk.Client.Common.RamSearchEngine private List _watchList = new List(); private readonly SearchEngineSettings _settings; - private readonly UndoHistory _history = new UndoHistory(true); + private readonly UndoHistory> _history = new UndoHistory>(true, new List()); //TODO use IList instead of IEnumerable and stop calling `.ToList()` (i.e. cloning) on reads and writes? private bool _isSorted = true; // Tracks whether or not the list is sorted by address, if it is, binary search can be used for finding watches public RamSearchEngine(SearchEngineSettings settings, IMemoryDomains memoryDomains) @@ -141,7 +141,7 @@ namespace BizHawk.Client.Common.RamSearchEngine if (UndoEnabled) { - _history.AddState(_watchList); + _history.AddState(_watchList.ToList()); } return before - _watchList.Count; @@ -244,7 +244,7 @@ namespace BizHawk.Client.Common.RamSearchEngine { if (UndoEnabled) { - _history.AddState(_watchList); + _history.AddState(_watchList.ToList()); } var addresses = watches.Select(w => w.Address); @@ -255,7 +255,7 @@ namespace BizHawk.Client.Common.RamSearchEngine { if (UndoEnabled) { - _history.AddState(_watchList); + _history.AddState(_watchList.ToList()); } var removeList = indices.Select(i => _watchList[i]); // This will fail after int.MaxValue but RAM Search fails on domains that large anyway diff --git a/src/BizHawk.Common/UndoHistory.cs b/src/BizHawk.Common/UndoHistory.cs index 66bedf1354..88969c92fb 100644 --- a/src/BizHawk.Common/UndoHistory.cs +++ b/src/BizHawk.Common/UndoHistory.cs @@ -1,81 +1,61 @@ using System.Collections.Generic; -using System.Linq; namespace BizHawk.Common { public class UndoHistory { - private List> _history = new List>(); - private int _curPos; // 1-based + private readonly T _blankState; - public int MaxUndoLevels { get; } + /// + /// 1-based, so the "current timeline" includes all of up to and not including _history[_curPos] + /// (that is, all of has been redo'd when _curPos == _history.Count) + /// + private int _curPos; - public UndoHistory(bool enabled) - { - MaxUndoLevels = 5; - Enabled = enabled; - } - - public bool Enabled { get; } - - public bool CanUndo => Enabled && _curPos > 1; + private readonly IList _history = new List(); public bool CanRedo => Enabled && _curPos < _history.Count; - public bool HasHistory => Enabled && _history.Any(); + public bool CanUndo => Enabled && _curPos > 1; + + public bool Enabled { get; } + + public bool HasHistory => Enabled && _history.Count != 0; + + /// + /// can actually grow to this + 1
+ /// TODO fix that by moving the .RemoveAt(0) loop in to AFTER the insertion
+ /// TODO old code assumed the setter was public, so pruning multiple states from start may have been required if this changed between insertions + ///
+ public int MaxUndoLevels { get; } = 5; + + /// + /// returned from calls to / when there is nothing to undo/redo, or + /// when either method is called while disabled + /// + public UndoHistory(bool enabled, T blankState) + { + _blankState = blankState; + Enabled = enabled; + } + + public void AddState(T newState) + { + if (!Enabled) return; + while (_history.Count > _curPos) _history.RemoveAt(_history.Count - 1); // prune "alternate future timeline" that we're about to overwrite + while (_history.Count > MaxUndoLevels) _history.RemoveAt(0); // prune from start when at user-defined limit + _history.Add(newState); + _curPos = _history.Count; + } public void Clear() { - _history = new List>(); + _history.Clear(); _curPos = 0; } - public void AddState(IEnumerable newState) - { - if (Enabled) - { - if (_curPos < _history.Count) - { - for (var i = _curPos + 1; i <= _history.Count; i++) - { - _history.Remove(_history[i - 1]); - } - } + public T Redo() => CanRedo && Enabled ? _history[++_curPos - 1] : _blankState; - // TODO: don't assume we are one over, since MaxUndoLevels is public, it could be set to a small number after a large list has occured - if (_history.Count > MaxUndoLevels) - { - foreach (var item in _history.Take(_history.Count - MaxUndoLevels)) - { - _history.Remove(item); - } - } - - _history.Add(newState.ToList()); - _curPos = _history.Count; - } - } - - public IEnumerable Undo() - { - if (CanUndo && Enabled) - { - _curPos--; - return _history[_curPos - 1]; - } - - return Enumerable.Empty(); - } - - public IEnumerable Redo() - { - if (CanRedo && Enabled) - { - _curPos++; - return _history[_curPos - 1]; - } - - return Enumerable.Empty(); - } + public T Undo() => CanUndo && Enabled ? _history[--_curPos - 1] : _blankState; } }