From 3516aa6d85049c20f34f4d5bec73a13ee0a70da8 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 20 Jul 2026 08:23:19 -0700 Subject: [PATCH] perf(advanced-search): span-based leaf evaluator for EvaluateBoolean Custom LeafEvaluator delegate takes the leaf as ReadOnlySpan (Func<> can't take a ref struct) and threads caller state through, so the property-test recursion allocates neither a per-leaf string nor a capturing closure per entity. --- .../AdvancedSearch/AdvancedSearchUtilitiesTests.cs | 3 ++- .../Advanced Search/AdvancedSearchThreadWorker.cs | 2 +- .../Advanced Search/AdvancedSearchUtilities.cs | 13 +++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs b/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs index 030f3f573..22923e53c 100644 --- a/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs @@ -51,7 +51,8 @@ public class AdvancedSearchUtilitiesTests [InlineData("F|F", false)] public void EvaluateBoolean_Precedence(string expr, bool expected) { - var result = AdvancedSearchUtilities.EvaluateBoolean(expr, leaf => leaf == "T"); + // State is unused here; the leaf evaluator just checks the span equals "T". + var result = AdvancedSearchUtilities.EvaluateBoolean(expr, 0, static (_, leaf) => leaf.SequenceEqual("T")); Assert.Equal(expected, result); } diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs index a350cbdcd..a4a26c244 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs @@ -349,7 +349,7 @@ public class AdvancedSearchThreadWorker } private static bool EvaluateRecursive(IEntity entity, ReadOnlySpan span) => - AdvancedSearchUtilities.EvaluateBoolean(span, leaf => EvaluateSingleExpression(entity, leaf)); + AdvancedSearchUtilities.EvaluateBoolean(span, entity, static (e, leaf) => EvaluateSingleExpression(e, leaf)); private static bool EvaluateSingleExpression(IEntity entity, ReadOnlySpan expression) { diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs index adc793241..b1735a67e 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs @@ -375,22 +375,27 @@ public static class AdvancedSearchUtilities return false; } + // Evaluates one trimmed leaf atom against caller-supplied state. A custom delegate is required + // because ReadOnlySpan cannot be a Func<> type argument; passing state avoids a per-call + // capturing closure, so the recursion allocates neither a string nor a closure. + internal delegate bool LeafEvaluator(TState state, ReadOnlySpan leaf); + // OR ('|') binds looser than AND ('@'); split on the outermost OR first, then AND. - internal static bool EvaluateBoolean(ReadOnlySpan expr, Func evalLeaf) + internal static bool EvaluateBoolean(ReadOnlySpan expr, TState state, LeafEvaluator evalLeaf) { var orIndex = expr.IndexOf('|'); if (orIndex != -1) { - return EvaluateBoolean(expr[..orIndex], evalLeaf) || EvaluateBoolean(expr[(orIndex + 1)..], evalLeaf); + return EvaluateBoolean(expr[..orIndex], state, evalLeaf) || EvaluateBoolean(expr[(orIndex + 1)..], state, evalLeaf); } var andIndex = expr.IndexOf('@'); if (andIndex != -1) { - return EvaluateBoolean(expr[..andIndex], evalLeaf) && EvaluateBoolean(expr[(andIndex + 1)..], evalLeaf); + return EvaluateBoolean(expr[..andIndex], state, evalLeaf) && EvaluateBoolean(expr[(andIndex + 1)..], state, evalLeaf); } - return evalLeaf(expr.Trim().ToString()); + return evalLeaf(state, expr.Trim()); } private static int GetEnumSize(Type enumType) =>