refactor: move Dalamud.Interface utils into main assembly, warnings pass

This commit is contained in:
goat 2023-08-04 19:36:09 +02:00
parent 758ae7c097
commit 02e1f2502e
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
80 changed files with 394 additions and 303 deletions

View file

@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Dalamud.Utility;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1618:Generic type parameters should be documented", Justification = "Reviewed,")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed,")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1615:Element return value should be documented", Justification = "Reviewed,")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1611:Element parameters should be documented", Justification = "Reviewed,")]
internal static class ArrayExtensions
{
/// <summary> Iterate over enumerables with additional index. </summary>
public static IEnumerable<(T Value, int Index)> WithIndex<T>(this IEnumerable<T> list)
=> list.Select((x, i) => (x, i));
/// <summary> Remove an added index from an indexed enumerable. </summary>
public static IEnumerable<T> WithoutIndex<T>(this IEnumerable<(T Value, int Index)> list)
=> list.Select(x => x.Value);
/// <summary> Remove the value and only keep the index from an indexed enumerable. </summary>
public static IEnumerable<int> WithoutValue<T>(this IEnumerable<(T Value, int Index)> list)
=> list.Select(x => x.Index);
// Find the index of the first object fulfilling predicate's criteria in the given list.
// Returns -1 if no such object is found.
public static int IndexOf<T>(this IEnumerable<T> array, Predicate<T> predicate)
{
var i = 0;
foreach (var obj in array)
{
if (predicate(obj))
return i;
++i;
}
return -1;
}
// Find the index of the first occurrence of needle in the given list.
// Returns -1 if needle is not contained in the list.
public static int IndexOf<T>(this IEnumerable<T> array, T needle) where T : notnull
{
var i = 0;
foreach (var obj in array)
{
if (needle.Equals(obj))
return i;
++i;
}
return -1;
}
// Find the first object fulfilling predicate's criteria in the given list, if one exists.
// Returns true if an object is found, false otherwise.
public static bool FindFirst<T>(this IEnumerable<T> array, Predicate<T> predicate, [NotNullWhen(true)] out T? result)
{
foreach (var obj in array)
{
if (predicate(obj))
{
result = obj!;
return true;
}
}
result = default;
return false;
}
// Find the first occurrence of needle in the given list and return the value contained in the list in result.
// Returns true if an object is found, false otherwise.
public static bool FindFirst<T>(this IEnumerable<T> array, T needle, [NotNullWhen(true)] out T? result) where T : notnull
{
foreach (var obj in array)
{
if (obj.Equals(needle))
{
result = obj;
return true;
}
}
result = default;
return false;
}
}

View file

@ -6,6 +6,9 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
#pragma warning disable SA1600
#pragma warning disable SA1602
internal readonly ref struct FuzzyMatcher
{
private static readonly (int, int)[] EmptySegArray = Array.Empty<(int, int)>();
@ -13,31 +16,31 @@ internal readonly ref struct FuzzyMatcher
private readonly string needleString = string.Empty;
private readonly ReadOnlySpan<char> needleSpan = ReadOnlySpan<char>.Empty;
private readonly int needleFinalPosition = -1;
private readonly (int start, int end)[] needleSegments = EmptySegArray;
private readonly (int Start, int End)[] needleSegments = EmptySegArray;
private readonly MatchMode mode = MatchMode.Simple;
public FuzzyMatcher(string term, MatchMode matchMode)
{
needleString = term;
needleSpan = needleString.AsSpan();
needleFinalPosition = needleSpan.Length - 1;
mode = matchMode;
this.needleString = term;
this.needleSpan = this.needleString.AsSpan();
this.needleFinalPosition = this.needleSpan.Length - 1;
this.mode = matchMode;
switch (matchMode)
{
case MatchMode.FuzzyParts:
needleSegments = FindNeedleSegments(needleSpan);
this.needleSegments = FindNeedleSegments(this.needleSpan);
break;
case MatchMode.Fuzzy:
case MatchMode.Simple:
needleSegments = EmptySegArray;
this.needleSegments = EmptySegArray;
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchMode), matchMode, null);
}
}
private static (int start, int end)[] FindNeedleSegments(ReadOnlySpan<char> span)
private static (int Start, int End)[] FindNeedleSegments(ReadOnlySpan<char> span)
{
var segments = new List<(int, int)>();
var wordStart = -1;
@ -66,37 +69,39 @@ internal readonly ref struct FuzzyMatcher
return segments.ToArray();
}
#pragma warning disable SA1202
public int Matches(string value)
#pragma warning restore SA1202
{
if (needleFinalPosition < 0)
if (this.needleFinalPosition < 0)
{
return 0;
}
if (mode == MatchMode.Simple)
if (this.mode == MatchMode.Simple)
{
return value.Contains(needleString) ? 1 : 0;
return value.Contains(this.needleString) ? 1 : 0;
}
var haystack = value.AsSpan();
if (mode == MatchMode.Fuzzy)
if (this.mode == MatchMode.Fuzzy)
{
return GetRawScore(haystack, 0, needleFinalPosition);
return this.GetRawScore(haystack, 0, this.needleFinalPosition);
}
if (mode == MatchMode.FuzzyParts)
if (this.mode == MatchMode.FuzzyParts)
{
if (needleSegments.Length < 2)
if (this.needleSegments.Length < 2)
{
return GetRawScore(haystack, 0, needleFinalPosition);
return this.GetRawScore(haystack, 0, this.needleFinalPosition);
}
var total = 0;
for (var i = 0; i < needleSegments.Length; i++)
for (var i = 0; i < this.needleSegments.Length; i++)
{
var (start, end) = needleSegments[i];
var cur = GetRawScore(haystack, start, end);
var (start, end) = this.needleSegments[i];
var cur = this.GetRawScore(haystack, start, end);
if (cur == 0)
{
return 0;
@ -116,7 +121,7 @@ internal readonly ref struct FuzzyMatcher
var max = 0;
for (var i = 0; i < values.Length; i++)
{
var cur = Matches(values[i]);
var cur = this.Matches(values[i]);
if (cur > max)
{
max = cur;
@ -128,7 +133,7 @@ internal readonly ref struct FuzzyMatcher
private int GetRawScore(ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
{
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needleStart, needleEnd);
var (startPos, gaps, consecutive, borderMatches, endPos) = this.FindForward(haystack, needleStart, needleEnd);
if (startPos < 0)
{
return 0;
@ -140,7 +145,7 @@ internal readonly ref struct FuzzyMatcher
// PluginLog.Debug(
// $"['{needleString.Substring(needleStart, needleEnd - needleStart + 1)}' in '{haystack}'] fwd: needleSize={needleSize} startPos={startPos} gaps={gaps} consecutive={consecutive} borderMatches={borderMatches} score={score}");
(startPos, gaps, consecutive, borderMatches) = FindReverse(haystack, endPos, needleStart, needleEnd);
(startPos, gaps, consecutive, borderMatches) = this.FindReverse(haystack, endPos, needleStart, needleEnd);
var revScore = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
// PluginLog.Debug(
// $"['{needleString.Substring(needleStart, needleEnd - needleStart + 1)}' in '{haystack}'] rev: needleSize={needleSize} startPos={startPos} gaps={gaps} consecutive={consecutive} borderMatches={borderMatches} score={revScore}");
@ -149,7 +154,9 @@ internal readonly ref struct FuzzyMatcher
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#pragma warning disable SA1204
private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches)
#pragma warning restore SA1204
{
var score = 100
+ needleSize * 3
@ -162,7 +169,7 @@ internal readonly ref struct FuzzyMatcher
return score < 1 ? 1 : score;
}
private (int startPos, int gaps, int consecutive, int borderMatches, int haystackIndex) FindForward(
private (int StartPos, int Gaps, int Consecutive, int BorderMatches, int HaystackIndex) FindForward(
ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
{
var needleIndex = needleStart;
@ -175,7 +182,7 @@ internal readonly ref struct FuzzyMatcher
for (var haystackIndex = 0; haystackIndex < haystack.Length; haystackIndex++)
{
if (haystack[haystackIndex] == needleSpan[needleIndex])
if (haystack[haystackIndex] == this.needleSpan[needleIndex])
{
#if BORDER_MATCHING
if (haystackIndex > 0)
@ -217,8 +224,8 @@ internal readonly ref struct FuzzyMatcher
return (-1, 0, 0, 0, 0);
}
private (int startPos, int gaps, int consecutive, int borderMatches) FindReverse(ReadOnlySpan<char> haystack,
int haystackLastMatchIndex, int needleStart, int needleEnd)
private (int StartPos, int Gaps, int Consecutive, int BorderMatches) FindReverse(
ReadOnlySpan<char> haystack, int haystackLastMatchIndex, int needleStart, int needleEnd)
{
var needleIndex = needleEnd;
var revLastMatchIndex = haystack.Length + 10;
@ -229,7 +236,7 @@ internal readonly ref struct FuzzyMatcher
for (var haystackIndex = haystackLastMatchIndex; haystackIndex >= 0; haystackIndex--)
{
if (haystack[haystackIndex] == needleSpan[needleIndex])
if (haystack[haystackIndex] == this.needleSpan[needleIndex])
{
#if BORDER_MATCHING
if (haystackIndex > 0)
@ -265,9 +272,12 @@ internal readonly ref struct FuzzyMatcher
}
}
public enum MatchMode
internal enum MatchMode
{
Simple,
Fuzzy,
FuzzyParts
FuzzyParts,
}
#pragma warning restore SA1600
#pragma warning restore SA1602

View file

@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Dalamud.Utility;
/// <summary>
/// Extensions methods providing stable insertion sorts for IList.
/// </summary>
internal static class StableInsertionSortExtension
{
/// <summary>
/// Perform a stable sort on a list.
/// </summary>
/// <param name="list">The list to sort.</param>
/// <param name="selector">Selector to order by.</param>
/// <typeparam name="T">Element type.</typeparam>
/// <typeparam name="TKey">Selected type.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void StableSort<T, TKey>(this IList<T> list, Func<T, TKey> selector)
{
var tmpList = new List<T>(list.Count);
tmpList.AddRange(list.OrderBy(selector));
for (var i = 0; i < tmpList.Count; ++i)
list[i] = tmpList[i];
}
/// <summary>
/// Perform a stable sort on a list.
/// </summary>
/// <param name="list">The list to sort.</param>
/// <param name="comparer">Comparer to use when comparing items.</param>
/// <typeparam name="T">Element type.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void StableSort<T>(this IList<T> list, Comparison<T> comparer)
{
var tmpList = new List<(T, int)>(list.Count);
tmpList.AddRange(list.WithIndex());
tmpList.Sort((a, b) =>
{
var ret = comparer(a.Item1, b.Item1);
return ret != 0 ? ret : a.Item2.CompareTo(b.Item2);
});
for (var i = 0; i < tmpList.Count; ++i)
list[i] = tmpList[i].Item1;
}
}

View file

@ -17,6 +17,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal;
using Dalamud.Networking.Http;
using ImGuiNET;