mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-28 23:10:22 +00:00
Reduce allocation when checking WordFilter
Nick/OT/HT were ToString()'d each legality analysis as the wordfilter couldn't check ReadOnlySpan<char> Now with .NET 9, we can use the AlternateLookup feature and now all calls are zero allocation for previously-seen strings.
This commit is contained in:
parent
6be99f6ad7
commit
0c210fdd11
3 changed files with 17 additions and 24 deletions
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -52,7 +52,8 @@ public static class WordFilter
|
|||
/// <summary>
|
||||
/// Due to some messages repeating (Trainer names), keep a list of repeated values for faster lookup.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, string?> Lookup = new(INIT_COUNT);
|
||||
private static readonly ConcurrentDictionary<string, string?>.AlternateLookup<ReadOnlySpan<char>> Lookup =
|
||||
new ConcurrentDictionary<string, string?>().GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
|
@ -60,45 +61,35 @@ public static class WordFilter
|
|||
/// <param name="message">Phrase to check for</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool IsFiltered(string message, [NotNullWhen(true)] out string? regMatch)
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message) || message.Length <= 1)
|
||||
if (message.IsWhiteSpace() || message.Length <= 1)
|
||||
{
|
||||
regMatch = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check dictionary
|
||||
lock (dictLock)
|
||||
{
|
||||
if (Lookup.TryGetValue(message, out regMatch))
|
||||
return regMatch != null;
|
||||
}
|
||||
if (Lookup.TryGetValue(message, out regMatch))
|
||||
return regMatch != null;
|
||||
|
||||
// Make the string lowercase invariant
|
||||
Span<char> lowercase = stackalloc char[message.Length];
|
||||
for (int i = 0; i < lowercase.Length; i++)
|
||||
lowercase[i] = char.ToLowerInvariant(message[i]);
|
||||
message.ToLowerInvariant(lowercase);
|
||||
|
||||
// not in dictionary, check patterns
|
||||
if (TryMatch(lowercase, out regMatch))
|
||||
{
|
||||
lock (dictLock)
|
||||
Lookup[message] = regMatch;
|
||||
Lookup.TryAdd(message, regMatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
// didn't match any pattern, cache result
|
||||
lock (dictLock)
|
||||
{
|
||||
if ((Lookup.Count & ~MAX_COUNT) != 0)
|
||||
Lookup.Clear(); // reset
|
||||
Lookup[message] = regMatch = null;
|
||||
}
|
||||
if ((Lookup.Dictionary.Count & ~MAX_COUNT) != 0)
|
||||
Lookup.Dictionary.Clear(); // reset
|
||||
Lookup.TryAdd(message, regMatch = null);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly object dictLock = new();
|
||||
private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size
|
||||
private const int INIT_COUNT = 1 << 10; // arbitrary init size to limit future doublings
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ public sealed class NicknameVerifier : Verifier
|
|||
// Non-nicknamed strings have already been checked.
|
||||
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format) && pk.IsNicknamed)
|
||||
{
|
||||
if (WordFilter.IsFiltered(nickname.ToString(), out var badPattern))
|
||||
if (WordFilter.IsFiltered(nickname, out var badPattern))
|
||||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||||
if (TrainerNameVerifier.ContainsTooManyNumbers(nickname, data.Info.Generation))
|
||||
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
|
||||
|
|
|
@ -54,12 +54,14 @@ public sealed class TrainerNameVerifier : Verifier
|
|||
|
||||
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format))
|
||||
{
|
||||
if (WordFilter.IsFiltered(trainer.ToString(), out var badPattern))
|
||||
if (WordFilter.IsFiltered(trainer, out var badPattern))
|
||||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||||
if (ContainsTooManyNumbers(trainer, data.Info.Generation))
|
||||
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
|
||||
|
||||
if (WordFilter.IsFiltered(pk.HandlingTrainerName, out badPattern))
|
||||
Span<char> ht = stackalloc char[pk.TrashCharCountTrainer];
|
||||
int nameLen = pk.LoadString(pk.HandlingTrainerTrash, ht);
|
||||
if (WordFilter.IsFiltered(ht[..nameLen], out badPattern))
|
||||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue