Refactor RamSearch to increase speed and improve functionality (#3954)

- merges `IMiniWatchDetailed` and `IMiniWatch` into one
- allow switching search modes without restarting the search
- general speed improvements due to less peeks when e.g. auto-search is toggled
- removes `PreviousType.LastSearch` due to its questionable use case
This commit is contained in:
Moritz Bender 2024-06-20 18:25:15 +02:00 committed by GitHub
parent 866fb86b60
commit 1a9e5e52f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 197 additions and 471 deletions

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Emulation.Common;
@ -7,43 +6,22 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
internal static class Extensions
{
public static float ToFloat(this long val)
{
var bytes = BitConverter.GetBytes((int)val);
return BitConverter.ToSingle(bytes, 0);
}
public static IEnumerable<IMiniWatch> ToBytes(this IEnumerable<long> addresses, SearchEngineSettings settings)
=> settings.IsDetailed()
? addresses.ToDetailedBytes(settings.Domain)
: addresses.ToBytes(settings.Domain);
=> addresses.ToDetailedBytes(settings.Domain);
public static IEnumerable<IMiniWatch> ToWords(this IEnumerable<long> addresses, SearchEngineSettings settings)
=> settings.IsDetailed()
? addresses.ToDetailedWords(settings.Domain, settings.BigEndian)
: addresses.ToWords(settings.Domain, settings.BigEndian);
=> addresses.ToDetailedWords(settings.Domain, settings.BigEndian);
public static IEnumerable<IMiniWatch> ToDWords(this IEnumerable<long> addresses, SearchEngineSettings settings)
=> settings.IsDetailed()
? addresses.ToDetailedDWords(settings.Domain, settings.BigEndian)
: addresses.ToDWords(settings.Domain, settings.BigEndian);
private static IEnumerable<IMiniWatch> ToBytes(this IEnumerable<long> addresses, MemoryDomain domain)
=> addresses.Select(a => new MiniByteWatch(domain, a));
=> addresses.ToDetailedDWords(settings.Domain, settings.BigEndian);
private static IEnumerable<IMiniWatch> ToDetailedBytes(this IEnumerable<long> addresses, MemoryDomain domain)
=> addresses.Select(a => new MiniByteWatchDetailed(domain, a));
private static IEnumerable<IMiniWatch> ToWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniWordWatch(domain, a, bigEndian));
=> addresses.Select(a => new MiniByteWatch(domain, a));
private static IEnumerable<IMiniWatch> ToDetailedWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniWordWatchDetailed(domain, a, bigEndian));
private static IEnumerable<IMiniWatch> ToDWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniDWordWatch(domain, a, bigEndian));
=> addresses.Select(a => new MiniWordWatch(domain, a, bigEndian));
private static IEnumerable<IMiniWatch> ToDetailedDWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniDWordWatchDetailed(domain, a, bigEndian));
=> addresses.Select(a => new MiniDWordWatch(domain, a, bigEndian));
}
}

View File

@ -4,130 +4,172 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
/// <summary>
/// Represents a Ram address for watching in the <see cref="RamSearchEngine" />
/// With the minimal details necessary for searching
/// </summary>
internal interface IMiniWatch
{
long Address { get; }
long Previous { get; } // do not store sign extended variables in here.
void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian);
long Current { get; }
int ChangeCount { get; }
void ClearChangeCount();
void SetPreviousToCurrent();
bool IsValid(MemoryDomain domain);
void Update(PreviousType type, MemoryDomain domain, bool bigEndian);
}
internal sealed class MiniByteWatch : IMiniWatch
{
public long Address { get; }
private byte _previous;
private byte _current;
public MiniByteWatch(MemoryDomain domain, long addr)
{
Address = addr;
_previous = GetByte(Address, domain);
_previous = _current = GetByte(domain);
}
public void SetPreviousToCurrent()
{
_previous = _current;
}
public long Previous => _previous;
public long Current => _current;
public bool IsValid(MemoryDomain domain)
{
return IsValid(Address, domain);
}
public int ChangeCount { get; private set; }
public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian)
public void Update(PreviousType type, MemoryDomain domain, bool bigEndian)
{
_previous = GetByte(Address, domain);
}
var newValue = GetByte(domain);
public static bool IsValid(long address, MemoryDomain domain)
{
return address < domain.Size;
}
public static byte GetByte(long address, MemoryDomain domain)
{
if (!IsValid(address, domain))
if (newValue != _current)
{
return 0;
ChangeCount++;
if (type == PreviousType.LastChange)
{
_previous = _current;
}
}
return domain.PeekByte(address);
if (type == PreviousType.LastFrame)
_previous = _current;
_current = newValue;
}
public void ClearChangeCount() => ChangeCount = 0;
public bool IsValid(MemoryDomain domain) => Address < domain.Size;
public byte GetByte(MemoryDomain domain)
{
return IsValid(domain) ? domain.PeekByte(Address) : (byte)0;
}
}
internal sealed class MiniWordWatch : IMiniWatch
{
public long Address { get; }
private ushort _previous;
private ushort _current;
public MiniWordWatch(MemoryDomain domain, long addr, bool bigEndian)
{
Address = addr;
_previous = GetUshort(Address, domain, bigEndian);
_previous = _current = GetUshort(domain, bigEndian);
}
public void SetPreviousToCurrent()
{
_previous = _current;
}
public long Previous => _previous;
public long Current => _current;
public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian)
{
_previous = GetUshort(Address, domain, bigEndian);
}
public int ChangeCount { get; private set; }
public bool IsValid(MemoryDomain domain)
public void Update(PreviousType type, MemoryDomain domain, bool bigEndian)
{
return IsValid(Address, domain);
}
var newValue = GetUshort(domain, bigEndian);
public static bool IsValid(long address, MemoryDomain domain)
{
return address < (domain.Size - 1);
}
public static ushort GetUshort(long address, MemoryDomain domain, bool bigEndian)
{
if (!IsValid(address, domain))
if (newValue != _current)
{
return 0;
ChangeCount++;
if (type == PreviousType.LastChange)
{
_previous = _current;
}
}
return domain.PeekUshort(address, bigEndian);
if (type == PreviousType.LastFrame)
_previous = _current;
_current = newValue;
}
public void ClearChangeCount() => ChangeCount = 0;
public bool IsValid(MemoryDomain domain) => Address < domain.Size - 1;
private ushort GetUshort(MemoryDomain domain, bool bigEndian)
{
return IsValid(domain) ? domain.PeekUshort(Address, bigEndian) : (ushort)0;
}
}
internal sealed class MiniDWordWatch : IMiniWatch
{
public long Address { get; }
private uint _previous;
private uint _current;
public MiniDWordWatch(MemoryDomain domain, long addr, bool bigEndian)
{
Address = addr;
_previous = GetUint(Address, domain, bigEndian);
_previous = _current = GetUint(domain, bigEndian);
}
public void SetPreviousToCurrent()
{
_previous = _current;
}
public long Previous => _previous;
public long Current => _current;
public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian)
{
_previous = GetUint(Address, domain, bigEndian);
}
public int ChangeCount { get; private set; }
public bool IsValid(MemoryDomain domain)
public void Update(PreviousType type, MemoryDomain domain, bool bigEndian)
{
return IsValid(Address, domain);
}
var newValue = GetUint(domain, bigEndian);
public static bool IsValid(long address, MemoryDomain domain)
{
return address < (domain.Size - 3);
}
public static uint GetUint(long address, MemoryDomain domain, bool bigEndian)
{
if (!IsValid(address, domain))
if (newValue != _current)
{
return 0;
ChangeCount++;
if (type == PreviousType.LastChange)
{
_previous = _current;
}
}
return domain.PeekUint(address, bigEndian);
if (type == PreviousType.LastFrame)
_previous = _current;
_current = newValue;
}
public void ClearChangeCount() => ChangeCount = 0;
public bool IsValid(MemoryDomain domain) => Address < domain.Size - 3;
private uint GetUint(MemoryDomain domain, bool bigEndian)
{
return IsValid(domain) ? domain.PeekUint(Address, bigEndian) : 0;
}
}
}

View File

@ -1,183 +0,0 @@
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common.RamSearchEngine
{
/// <summary>
/// Represents a <see cref="IMiniWatch" /> but with added details
/// to do change tracking. These types add more information but at a cost of
/// having to poll the ram address on every update
/// </summary>
internal interface IMiniWatchDetails : IMiniWatch
{
int ChangeCount { get; }
void ClearChangeCount();
void Update(PreviousType type, MemoryDomain domain, bool bigEndian);
}
internal sealed class MiniByteWatchDetailed : IMiniWatchDetails
{
public long Address { get; }
private byte _previous;
private byte _prevFrame;
public MiniByteWatchDetailed(MemoryDomain domain, long addr)
{
Address = addr;
SetPreviousToCurrent(domain, false);
}
public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian)
{
_previous = _prevFrame = MiniByteWatch.GetByte(Address, domain);
}
public long Previous => _previous;
public int ChangeCount { get; private set; }
public void Update(PreviousType type, MemoryDomain domain, bool bigEndian)
{
var value = MiniByteWatch.GetByte(Address, domain);
if (value != _prevFrame)
{
ChangeCount++;
}
switch (type)
{
case PreviousType.Original:
case PreviousType.LastSearch:
break;
case PreviousType.LastFrame:
_previous = _prevFrame;
break;
case PreviousType.LastChange:
if (_prevFrame != value)
{
_previous = _prevFrame;
}
break;
}
_prevFrame = value;
}
public void ClearChangeCount() => ChangeCount = 0;
public bool IsValid(MemoryDomain domain) => MiniByteWatch.IsValid(Address, domain);
}
internal sealed class MiniWordWatchDetailed : IMiniWatchDetails
{
public long Address { get; }
private ushort _previous;
private ushort _prevFrame;
public MiniWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian)
{
Address = addr;
SetPreviousToCurrent(domain, bigEndian);
}
public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian)
{
_previous = _prevFrame = MiniWordWatch.GetUshort(Address, domain, bigEndian);
}
public long Previous => _previous;
public int ChangeCount { get; private set; }
public void Update(PreviousType type, MemoryDomain domain, bool bigEndian)
{
var value = MiniWordWatch.GetUshort(Address, domain, bigEndian);
if (value != Previous)
{
ChangeCount++;
}
switch (type)
{
case PreviousType.Original:
case PreviousType.LastSearch:
break;
case PreviousType.LastFrame:
_previous = _prevFrame;
break;
case PreviousType.LastChange:
if (_prevFrame != value)
{
_previous = _prevFrame;
}
break;
}
_prevFrame = value;
}
public void ClearChangeCount() => ChangeCount = 0;
public bool IsValid(MemoryDomain domain) => MiniWordWatch.IsValid(Address, domain);
}
internal sealed class MiniDWordWatchDetailed : IMiniWatchDetails
{
public long Address { get; }
private uint _previous;
private uint _prevFrame;
public MiniDWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian)
{
Address = addr;
SetPreviousToCurrent(domain, bigEndian);
}
public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian)
{
_previous = _prevFrame = MiniDWordWatch.GetUint(Address, domain, bigEndian);
}
public long Previous => (int)_previous;
public int ChangeCount { get; private set; }
public void Update(PreviousType type, MemoryDomain domain, bool bigEndian)
{
var value = MiniDWordWatch.GetUint(Address, domain, bigEndian);
if (value != Previous)
{
ChangeCount++;
}
switch (type)
{
case PreviousType.Original:
case PreviousType.LastSearch:
break;
case PreviousType.LastFrame:
_previous = _prevFrame;
break;
case PreviousType.LastChange:
if (_prevFrame != value)
{
_previous = _prevFrame;
}
break;
}
_prevFrame = value;
}
public void ClearChangeCount() => ChangeCount = 0;
public bool IsValid(MemoryDomain domain) => MiniDWordWatch.IsValid(Address, domain);
}
}

View File

@ -21,10 +21,9 @@ namespace BizHawk.Client.Common.RamSearchEngine
private Compare _compareTo = Compare.Previous;
private IMiniWatch[] _watchList = Array.Empty<IMiniWatch>();
private IMiniWatch[] _watchList = [ ];
private readonly SearchEngineSettings _settings;
private readonly UndoHistory<IEnumerable<IMiniWatch>> _history = new UndoHistory<IEnumerable<IMiniWatch>>(true, new List<IMiniWatch>()); //TODO use IList instead of IEnumerable and stop calling `.ToArray()` (i.e. cloning) on reads and writes?
private bool _isSorted = true; // Tracks whether or not the array is sorted by address, if it is, binary search can be used for finding watches
private readonly UndoHistory<IEnumerable<IMiniWatch>> _history = new(true, [ ]); //TODO use IList instead of IEnumerable and stop calling `.ToArray()` (i.e. cloning) on reads and writes?
public RamSearchEngine(SearchEngineSettings settings, IMemoryDomains memoryDomains)
{
@ -65,46 +64,13 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
default:
case WatchSize.Byte:
if (_settings.IsDetailed())
{
for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniByteWatchDetailed(domain, i);
}
else
{
for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniByteWatch(domain, i);
}
for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniByteWatch(domain, i);
break;
case WatchSize.Word:
if (_settings.IsDetailed())
{
for (var i = 0; i < _watchList.Length; i++)
{
_watchList[i] = new MiniWordWatchDetailed(domain, i * stepSize, _settings.BigEndian);
}
}
else
{
for (var i = 0; i < _watchList.Length; i++)
{
_watchList[i] = new MiniWordWatch(domain, i * stepSize, _settings.BigEndian);
}
}
for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniWordWatch(domain, i * stepSize, _settings.BigEndian);
break;
case WatchSize.DWord:
if (_settings.IsDetailed())
{
for (var i = 0; i < _watchList.Length; i++)
{
_watchList[i] = new MiniDWordWatchDetailed(domain, i * stepSize, _settings.BigEndian);
}
}
else
{
for (var i = 0; i < _watchList.Length; i++)
{
_watchList[i] = new MiniDWordWatch(domain, i * stepSize, _settings.BigEndian);
}
}
for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniDWordWatch(domain, i * stepSize, _settings.BigEndian);
break;
}
}
@ -122,12 +88,14 @@ namespace BizHawk.Client.Common.RamSearchEngine
"",
0,
_watchList[index].Previous,
_settings.IsDetailed() ? ((IMiniWatchDetails)_watchList[index]).ChangeCount : 0);
_settings.IsDetailed() ? _watchList[index].ChangeCount : 0);
public int DoSearch()
public int DoSearch(bool updatePrevious)
{
int before = _watchList.Length;
Update(updatePrevious);
using (Domain.EnterExit())
{
_watchList = _compareTo switch
@ -139,11 +107,6 @@ namespace BizHawk.Client.Common.RamSearchEngine
Compare.Difference => CompareDifference(_watchList).ToArray(),
_ => ComparePrevious(_watchList).ToArray()
};
if (_settings.PreviousType == PreviousType.LastSearch)
{
SetPreviousToCurrent();
}
}
if (UndoEnabled)
@ -154,11 +117,11 @@ namespace BizHawk.Client.Common.RamSearchEngine
return before - _watchList.Length;
}
public bool Preview(long address)
public bool Preview(int index)
{
var listOfOne = Enumerable.Repeat(_isSorted
? _watchList.BinarySearch(w => w.Address, address)
: _watchList.FirstOrDefault(w => w.Address == address), 1);
var addressWatch = _watchList[index];
addressWatch.Update(PreviousType.Original, _settings.Domain, _settings.BigEndian);
IMiniWatch[] listOfOne = [ addressWatch ];
return _compareTo switch
{
@ -204,13 +167,12 @@ namespace BizHawk.Client.Common.RamSearchEngine
/// </remarks>
public int? DifferentBy { get; set; }
public void Update()
public void Update(bool updatePrevious)
{
if (!_settings.IsDetailed()) return;
using var @lock = _settings.Domain.EnterExit();
foreach (IMiniWatchDetails watch in _watchList)
foreach (var watch in _watchList)
{
watch.Update(_settings.PreviousType, _settings.Domain, _settings.BigEndian);
watch.Update(updatePrevious ? _settings.PreviousType : PreviousType.Original, _settings.Domain, _settings.BigEndian);
}
}
@ -218,26 +180,18 @@ namespace BizHawk.Client.Common.RamSearchEngine
public void SetEndian(bool bigEndian) => _settings.BigEndian = bigEndian;
/// <exception cref="InvalidOperationException"><see cref="Mode"/> is <see cref="SearchMode.Fast"/> and <paramref name="type"/> is <see cref="PreviousType.LastFrame"/></exception>
public void SetPreviousType(PreviousType type)
{
if (_settings.IsFastMode() && type == PreviousType.LastFrame)
{
throw new InvalidOperationException();
}
public void SetPreviousType(PreviousType type) => _settings.PreviousType = type;
_settings.PreviousType = type;
}
public void SetMode(SearchMode mode) => _settings.Mode = mode;
public void SetPreviousToCurrent()
{
Array.ForEach(_watchList, w => w.SetPreviousToCurrent(_settings.Domain, _settings.BigEndian));
Array.ForEach(_watchList, static w => w.SetPreviousToCurrent());
}
public void ClearChangeCounts()
{
if (!_settings.IsDetailed()) return;
foreach (var watch in _watchList.Cast<IMiniWatchDetails>())
foreach (var watch in _watchList)
{
watch.ClearChangeCount();
}
@ -285,33 +239,26 @@ namespace BizHawk.Client.Common.RamSearchEngine
};
_watchList = (append ? _watchList.Concat(list) : list).ToArray();
_isSorted = false; //TODO can this be smarter, such as by inserting instead of appending?
}
public void Sort(string column, bool reverse)
{
_isSorted = column == WatchList.Address && !reverse;
switch (column)
{
case WatchList.Address:
_watchList = _watchList.OrderBy(w => w.Address, reverse).ToArray();
break;
case WatchList.Value:
_watchList = _watchList.OrderBy(w => GetValue(w.Address), reverse).ToArray();
_watchList = _watchList.OrderBy(w => w.Current, reverse).ToArray();
break;
case WatchList.Prev:
_watchList = _watchList.OrderBy(w => w.Previous, reverse).ToArray();
break;
case WatchList.ChangesCol:
if (!_settings.IsDetailed()) break;
_watchList = _watchList
.Cast<IMiniWatchDetails>()
.OrderBy(w => w.ChangeCount, reverse)
.Cast<IMiniWatch>()
.ToArray();
_watchList = _watchList.OrderBy(w => w.ChangeCount, reverse).ToArray();
break;
case WatchList.Diff:
_watchList = _watchList.OrderBy(w => GetValue(w.Address) - w.Previous, reverse).ToArray();
_watchList = _watchList.OrderBy(w => w.Current - w.Previous, reverse).ToArray();
break;
}
}
@ -360,52 +307,52 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
default:
case ComparisonOperator.Equal:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(w.Previous));
return watchList.Where(w => SignExtendAsNeeded(w.Current) == SignExtendAsNeeded(w.Previous));
case ComparisonOperator.NotEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(w.Previous));
return watchList.Where(w => SignExtendAsNeeded(w.Current) != SignExtendAsNeeded(w.Previous));
case ComparisonOperator.GreaterThan:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(w.Previous));
return watchList.Where(w => SignExtendAsNeeded(w.Current) > SignExtendAsNeeded(w.Previous));
case ComparisonOperator.GreaterThanEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(w.Previous));
return watchList.Where(w => SignExtendAsNeeded(w.Current) >= SignExtendAsNeeded(w.Previous));
case ComparisonOperator.LessThan:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(w.Previous));
return watchList.Where(w => SignExtendAsNeeded(w.Current) < SignExtendAsNeeded(w.Previous));
case ComparisonOperator.LessThanEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(w.Previous));
return watchList.Where(w => SignExtendAsNeeded(w.Current) <= SignExtendAsNeeded(w.Previous));
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
return watchList.Where(w =>
differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous)));
differentBy == Math.Abs(SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous)));
}
}
switch (Operator)
{
default:
case ComparisonOperator.Equal:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(ReinterpretAsF32(w.Previous)));
return watchList.Where(w => ReinterpretAsF32(w.Current).HawkFloatEquality(ReinterpretAsF32(w.Previous)));
case ComparisonOperator.NotEqual:
return watchList.Where(w => !ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(ReinterpretAsF32(w.Previous)));
return watchList.Where(w => !ReinterpretAsF32(w.Current).HawkFloatEquality(ReinterpretAsF32(w.Previous)));
case ComparisonOperator.GreaterThan:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) > ReinterpretAsF32(w.Previous));
return watchList.Where(w => ReinterpretAsF32(w.Current) > ReinterpretAsF32(w.Previous));
case ComparisonOperator.GreaterThanEqual:
return watchList.Where(w =>
{
var val = ReinterpretAsF32(GetValue(w.Address));
var val = ReinterpretAsF32(w.Current);
var prev = ReinterpretAsF32(w.Previous);
return val > prev || val.HawkFloatEquality(prev);
});
case ComparisonOperator.LessThan:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) < ReinterpretAsF32(w.Previous));
return watchList.Where(w => ReinterpretAsF32(w.Current) < ReinterpretAsF32(w.Previous));
case ComparisonOperator.LessThanEqual:
return watchList.Where(w =>
{
var val = ReinterpretAsF32(GetValue(w.Address));
var val = ReinterpretAsF32(w.Current);
var prev = ReinterpretAsF32(w.Previous);
return val < prev || val.HawkFloatEquality(prev);
});
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
var differentByF = ReinterpretAsF32(differentBy);
return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous))
return watchList.Where(w => Math.Abs(ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous))
.HawkFloatEquality(differentByF));
}
}
@ -419,21 +366,21 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
default:
case ComparisonOperator.Equal:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(compareValue));
return watchList.Where(w => SignExtendAsNeeded(w.Current) == SignExtendAsNeeded(compareValue));
case ComparisonOperator.NotEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(compareValue));
return watchList.Where(w => SignExtendAsNeeded(w.Current) != SignExtendAsNeeded(compareValue));
case ComparisonOperator.GreaterThan:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(compareValue));
return watchList.Where(w => SignExtendAsNeeded(w.Current) > SignExtendAsNeeded(compareValue));
case ComparisonOperator.GreaterThanEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(compareValue));
return watchList.Where(w => SignExtendAsNeeded(w.Current) >= SignExtendAsNeeded(compareValue));
case ComparisonOperator.LessThan:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(compareValue));
return watchList.Where(w => SignExtendAsNeeded(w.Current) < SignExtendAsNeeded(compareValue));
case ComparisonOperator.LessThanEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(compareValue));
return watchList.Where(w => SignExtendAsNeeded(w.Current) <= SignExtendAsNeeded(compareValue));
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
return watchList.Where(w =>
differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(compareValue)));
differentBy == Math.Abs(SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(compareValue)));
}
}
var compareValueF = ReinterpretAsF32(compareValue);
@ -441,29 +388,29 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
default:
case ComparisonOperator.Equal:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(compareValueF));
return watchList.Where(w => ReinterpretAsF32(w.Current).HawkFloatEquality(compareValueF));
case ComparisonOperator.NotEqual:
return watchList.Where(w => !ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(compareValueF));
return watchList.Where(w => !ReinterpretAsF32(w.Current).HawkFloatEquality(compareValueF));
case ComparisonOperator.GreaterThan:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) > compareValueF);
return watchList.Where(w => ReinterpretAsF32(w.Current) > compareValueF);
case ComparisonOperator.GreaterThanEqual:
return watchList.Where(w =>
{
var val = ReinterpretAsF32(GetValue(w.Address));
var val = ReinterpretAsF32(w.Current);
return val > compareValueF || val.HawkFloatEquality(compareValueF);
});
case ComparisonOperator.LessThan:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) < compareValueF);
return watchList.Where(w => ReinterpretAsF32(w.Current) < compareValueF);
case ComparisonOperator.LessThanEqual:
return watchList.Where(w =>
{
var val = ReinterpretAsF32(GetValue(w.Address));
var val = ReinterpretAsF32(w.Current);
return val < compareValueF || val.HawkFloatEquality(compareValueF);
});
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
var differentByF = ReinterpretAsF32(differentBy);
return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w.Address)) - compareValueF)
return watchList.Where(w => Math.Abs(ReinterpretAsF32(w.Current) - compareValueF)
.HawkFloatEquality(differentByF));
}
}
@ -494,40 +441,25 @@ namespace BizHawk.Client.Common.RamSearchEngine
private IEnumerable<IMiniWatch> CompareChanges(IEnumerable<IMiniWatch> watchList)
{
if (!_settings.IsDetailed()) throw new InvalidCastException(); //TODO matches previous behaviour; was this intended to skip processing? --yoshi
if (CompareValue is not long compareValue) throw new InvalidCastException(); //TODO typo for IOE?
if (CompareValue is not long compareValue) throw new InvalidOperationException();
switch (Operator)
{
default:
case ComparisonOperator.Equal:
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => w.ChangeCount == compareValue);
return watchList.Where(w => w.ChangeCount == compareValue);
case ComparisonOperator.NotEqual:
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => w.ChangeCount != compareValue);
return watchList.Where(w => w.ChangeCount != compareValue);
case ComparisonOperator.GreaterThan:
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => w.ChangeCount > compareValue);
return watchList.Where(w => w.ChangeCount > compareValue);
case ComparisonOperator.GreaterThanEqual:
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => w.ChangeCount >= compareValue);
return watchList.Where(w => w.ChangeCount >= compareValue);
case ComparisonOperator.LessThan:
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => w.ChangeCount < compareValue);
return watchList.Where(w => w.ChangeCount < compareValue);
case ComparisonOperator.LessThanEqual:
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => w.ChangeCount <= compareValue);
return watchList.Where(w => w.ChangeCount <= compareValue);
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
return watchList
.Cast<IMiniWatchDetails>()
.Where(w => Math.Abs(w.ChangeCount - compareValue) == differentBy);
return watchList.Where(w => Math.Abs(w.ChangeCount - compareValue) == differentBy);
}
}
@ -540,21 +472,21 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
default:
case ComparisonOperator.Equal:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) == compareValue);
return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) == compareValue);
case ComparisonOperator.NotEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) != compareValue);
return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) != compareValue);
case ComparisonOperator.GreaterThan:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) > compareValue);
return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) > compareValue);
case ComparisonOperator.GreaterThanEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) >= compareValue);
return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) >= compareValue);
case ComparisonOperator.LessThan:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) < compareValue);
return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) < compareValue);
case ComparisonOperator.LessThanEqual:
return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) <= compareValue);
return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) <= compareValue);
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
return watchList.Where(w =>
differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) - compareValue));
differentBy == Math.Abs(SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) - compareValue));
}
}
var compareValueF = ReinterpretAsF32(compareValue);
@ -562,29 +494,29 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
default:
case ComparisonOperator.Equal:
return watchList.Where(w => (ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF));
return watchList.Where(w => (ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF));
case ComparisonOperator.NotEqual:
return watchList.Where(w => !(ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF));
return watchList.Where(w => !(ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF));
case ComparisonOperator.GreaterThan:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous) > compareValueF);
return watchList.Where(w => ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous) > compareValueF);
case ComparisonOperator.GreaterThanEqual:
return watchList.Where(w =>
{
var diff = ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous);
var diff = ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous);
return diff > compareValueF || diff.HawkFloatEquality(compareValueF);
});
case ComparisonOperator.LessThan:
return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous) < compareValueF);
return watchList.Where(w => ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous) < compareValueF);
case ComparisonOperator.LessThanEqual:
return watchList.Where(w =>
{
var diff = ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous);
var diff = ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous);
return diff < compareValueF || diff.HawkFloatEquality(compareValueF);
});
case ComparisonOperator.DifferentBy:
if (DifferentBy is not int differentBy) throw new InvalidOperationException();
var differentByF = ReinterpretAsF32(differentBy);
return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous) - compareValueF)
return watchList.Where(w => Math.Abs(ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous) - compareValueF)
.HawkFloatEquality(differentByF));
}
}
@ -605,24 +537,12 @@ namespace BizHawk.Client.Common.RamSearchEngine
};
}
private long GetValue(long addr)
{
// do not return sign extended variables from here.
return _settings.Size switch
{
WatchSize.Byte => MiniByteWatch.GetByte(addr, Domain),
WatchSize.Word => MiniWordWatch.GetUshort(addr, Domain, _settings.BigEndian),
WatchSize.DWord => MiniDWordWatch.GetUint(addr, Domain, _settings.BigEndian),
_ => MiniByteWatch.GetByte(addr, Domain)
};
}
private bool CanDoCompareType(Compare compareType)
{
return _settings.Mode switch
{
SearchMode.Detailed => true,
SearchMode.Fast => (compareType != Compare.Changes),
SearchMode.Fast => compareType != Compare.Changes,
_ => true
};
}

View File

@ -8,28 +8,25 @@ namespace BizHawk.Client.Common.RamSearchEngine
{
BigEndian = memoryDomains.MainMemory.EndianType == MemoryDomain.Endian.Big;
Size = (WatchSize)memoryDomains.MainMemory.WordSize;
Type = WatchDisplayType.Unsigned;
Mode = memoryDomains.MainMemory.Size > 1024 * 1024
? SearchMode.Fast
: SearchMode.Detailed;
Domain = memoryDomains.MainMemory;
CheckMisAligned = false;
PreviousType = PreviousType.LastSearch;
UseUndoHistory = useUndoHistory;
}
/*Require restart*/
public SearchMode Mode { get; set; }
public MemoryDomain Domain { get; set; }
public WatchSize Size { get; set; }
public bool CheckMisAligned { get; set; }
/*Can be changed mid-search*/
public WatchDisplayType Type { get; set; }
public WatchDisplayType Type { get; set; } = WatchDisplayType.Unsigned;
public bool BigEndian { get; set; }
public PreviousType PreviousType { get; set; }
public PreviousType PreviousType { get; set; } = PreviousType.LastFrame;
public bool UseUndoHistory { get; set; }
public SearchMode Mode { get; set; }
}
public static class SearchEngineSettingsExtensions

View File

@ -2,9 +2,8 @@
{
public enum PreviousType
{
Original = 0,
LastSearch = 1,
LastFrame = 2,
LastChange = 3
Original,
LastFrame,
LastChange,
}
}

View File

@ -71,7 +71,6 @@ namespace BizHawk.Client.EmuHawk
this.DisplayTypeSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.toolStripSeparator1 = new BizHawk.WinForms.Controls.ToolStripSeparatorEx();
this.DefinePreviousValueSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.Previous_LastSearchMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.PreviousFrameMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.Previous_OriginalMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.Previous_LastChangeMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
@ -397,18 +396,12 @@ namespace BizHawk.Client.EmuHawk
// DefinePreviousValueSubMenu
//
this.DefinePreviousValueSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.Previous_LastSearchMenuItem,
this.PreviousFrameMenuItem,
this.Previous_OriginalMenuItem,
this.Previous_LastChangeMenuItem});
this.DefinePreviousValueSubMenu.Text = "Define Previous Value";
this.DefinePreviousValueSubMenu.DropDownOpened += new System.EventHandler(this.DefinePreviousValueSubMenu_DropDownOpened);
//
// Previous_LastSearchMenuItem
//
this.Previous_LastSearchMenuItem.Text = "Last &Search";
this.Previous_LastSearchMenuItem.Click += new System.EventHandler(this.Previous_LastSearchMenuItem_Click);
//
// PreviousFrameMenuItem
//
this.PreviousFrameMenuItem.Text = "&Previous Frame";
@ -1132,7 +1125,6 @@ namespace BizHawk.Client.EmuHawk
private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator8;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx DefinePreviousValueSubMenu;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx PreviousFrameMenuItem;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx Previous_LastSearchMenuItem;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx Previous_OriginalMenuItem;
private System.Windows.Forms.GroupBox CompareToBox;
private System.Windows.Forms.RadioButton DifferenceRadio;
@ -1185,4 +1177,4 @@ namespace BizHawk.Client.EmuHawk
private BizHawk.WinForms.Controls.ToolStripMenuItemEx AutoSearchAccountForLagMenuItem;
private ToolStripMenuItemEx SelectAllMenuItem;
}
}
}

View File

@ -189,7 +189,7 @@ namespace BizHawk.Client.EmuHawk
var nextColor = Color.White;
var search = _searches[index];
var isCheat = MainForm.CheatList.IsActive(_settings.Domain, search.Address);
var isWeeded = Settings.PreviewMode && !_forcePreviewClear && _searches.Preview(search.Address);
var isWeeded = Settings.PreviewMode && !_forcePreviewClear && _searches.Preview(index);
if (!search.IsValid)
{
@ -271,8 +271,6 @@ namespace BizHawk.Client.EmuHawk
{
if (_searches.Count > 0)
{
_searches.Update();
if (_autoSearch)
{
if (InputPollableCore != null && Settings.AutoSearchTakeLagFramesIntoAccount && InputPollableCore.IsLagFrame)
@ -281,21 +279,24 @@ namespace BizHawk.Client.EmuHawk
}
else
{
DoSearch();
DoSearch(true);
}
}
else if (_settings.IsDetailed())
{
_searches.Update(true);
}
_forcePreviewClear = false;
WatchListView.RowCount = _searches.Count;
}
}
// TODO: this seems to be missing some logic from FrameUpdate that probably should exist here
private void MinimalUpdate()
{
if (_searches.Count > 0)
{
_searches.Update();
if (_autoSearch)
{
DoSearch();
@ -522,14 +523,14 @@ namespace BizHawk.Client.EmuHawk
}
}
public void DoSearch()
public void DoSearch(bool updatePrevious = false)
{
_searches.CompareValue = CompareToValue;
_searches.DifferentBy = DifferentByValue;
_searches.Operator = Operator;
_searches.CompareTo = Compare;
var removed = _searches.DoSearch();
var removed = _searches.DoSearch(updatePrevious);
UpdateList();
SetRemovedMessage(removed);
ToggleSearchDependentToolBarItems();
@ -588,7 +589,6 @@ namespace BizHawk.Client.EmuHawk
&& _settings.IsDetailed())
{
_settings.Mode = SearchMode.Fast;
SetReboot(true);
MessageLabel.Text = "Large domain, switching to fast mode";
}
}
@ -754,6 +754,7 @@ namespace BizHawk.Client.EmuHawk
private void SetToDetailedMode()
{
_settings.Mode = SearchMode.Detailed;
_searches.SetMode(SearchMode.Detailed);
NumberOfChangesRadio.Enabled = true;
NumberOfChangesBox.Enabled = true;
DifferenceRadio.Enabled = true;
@ -764,7 +765,6 @@ namespace BizHawk.Client.EmuHawk
ChangesMenuItem.Checked = true;
ColumnToggleCallback();
SetReboot(true);
}
private ToolStripMenuItem ChangesMenuItem
@ -783,11 +783,7 @@ namespace BizHawk.Client.EmuHawk
private void SetToFastMode()
{
_settings.Mode = SearchMode.Fast;
if (_settings.PreviousType == PreviousType.LastFrame || _settings.PreviousType == PreviousType.LastChange)
{
SetPreviousType(PreviousType.LastSearch);
}
_searches.SetMode(SearchMode.Fast);
NumberOfChangesRadio.Enabled = false;
NumberOfChangesBox.Enabled = false;
@ -803,7 +799,6 @@ namespace BizHawk.Client.EmuHawk
ChangesMenuItem.Checked = false;
ColumnToggleCallback();
SetReboot(true);
}
private void RemoveAddresses()
@ -1085,7 +1080,6 @@ namespace BizHawk.Client.EmuHawk
private void DefinePreviousValueSubMenu_DropDownOpened(object sender, EventArgs e)
{
Previous_LastSearchMenuItem.Checked = false;
PreviousFrameMenuItem.Checked = false;
Previous_OriginalMenuItem.Checked = false;
Previous_LastChangeMenuItem.Checked = false;
@ -1093,9 +1087,6 @@ namespace BizHawk.Client.EmuHawk
switch (_settings.PreviousType)
{
default:
case PreviousType.LastSearch:
Previous_LastSearchMenuItem.Checked = true;
break;
case PreviousType.LastFrame:
PreviousFrameMenuItem.Checked = true;
break;
@ -1106,9 +1097,6 @@ namespace BizHawk.Client.EmuHawk
Previous_LastChangeMenuItem.Checked = true;
break;
}
PreviousFrameMenuItem.Enabled = _settings.IsDetailed();
Previous_LastChangeMenuItem.Enabled = _settings.IsDetailed();
}
private void DetailedMenuItem_Click(object sender, EventArgs e)
@ -1147,11 +1135,6 @@ namespace BizHawk.Client.EmuHawk
SetPreviousType(PreviousType.LastFrame);
}
private void Previous_LastSearchMenuItem_Click(object sender, EventArgs e)
{
SetPreviousType(PreviousType.LastSearch);
}
private void Previous_OriginalMenuItem_Click(object sender, EventArgs e)
{
SetPreviousType(PreviousType.Original);
@ -1170,8 +1153,6 @@ namespace BizHawk.Client.EmuHawk
private void SearchSubMenu_DropDownOpened(object sender, EventArgs e)
{
ClearChangeCountsMenuItem.Enabled = _settings.IsDetailed();
RemoveMenuItem.Enabled =
AddToRamWatchMenuItem.Enabled =
WatchListView.AnyRowsSelected;