namespace WallstopStudios.DataVisualizer.Editor.Data { using System; using System.Collections.Generic; /// /// Pure evaluation of a against an object's Unity asset /// labels. The AND/OR combination rules previously lived in two copies (the live filter loop and /// a dead helper) which drifted apart; consolidating them here fixes that and lets the rules be /// unit tested without an editor window or real assets. /// /// Public because the package's editor test assembly cannot see internals of the editor /// assembly (InternalsVisibleTo is not honored for that pair in Unity's compilation). /// public static class LabelFilterEvaluator { /// /// Returns whether satisfy . An /// empty AND or OR clause is treated as "no constraint". In OR mode an object must satisfy at /// least one active clause; an empty clause never counts as a match — otherwise every /// object would pass whenever only the other clause was populated (the historical bug where /// OR-mode with only OR labels matched everything). /// public static bool Matches(IReadOnlyList objectLabels, TypeLabelFilterConfig config) { if (config == null) { return true; } List andLabels = config.andLabels; List orLabels = config.orLabels; bool hasAnd = andLabels is { Count: > 0 }; bool hasOr = orLabels is { Count: > 0 }; if (!hasAnd && !hasOr) { return true; } IReadOnlyList present = objectLabels ?? Array.Empty(); bool andSatisfied = hasAnd && AllPresent(andLabels, present); bool orSatisfied = hasOr && AnyPresent(orLabels, present); return config.combinationType == LabelCombinationType.Or ? andSatisfied || orSatisfied // at least one ACTIVE clause must match : (!hasAnd || andSatisfied) && (!hasOr || orSatisfied); // every active clause // Local, non-capturing helpers keep this allocation-free: Matches runs once per object // during filtering, so a per-call HashSet or closure would add real GC pressure. Asset // label sets are tiny, so linear membership over the provided list is cheap. static bool AllPresent(List required, IReadOnlyList labels) { for (int i = 0; i < required.Count; i++) { if (!Contains(labels, required[i])) { return false; } } return true; } static bool AnyPresent(List candidates, IReadOnlyList labels) { for (int i = 0; i < candidates.Count; i++) { if (Contains(labels, candidates[i])) { return true; } } return false; } static bool Contains(IReadOnlyList labels, string target) { for (int i = 0; i < labels.Count; i++) { if (string.Equals(labels[i], target, StringComparison.Ordinal)) { return true; } } return false; } } } }