Skip to content

Commit 7f36e6b

Browse files
committed
fix: picker names confusion
1 parent 5b6d4bc commit 7f36e6b

3 files changed

Lines changed: 58 additions & 28 deletions

File tree

CHANGELOG.MD

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ All notable changes to this project will be documented in this file.
33

44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6-
[Unreleased]
6+
7+
## [Unreleased]
8+
### Changed
9+
- Reverted `CollectionItemQuery.MatchType` enum values: `SomeNot``NotAny`, `None``NotAll`. The new names mirror `Any`/`All` and describe the actual semantics (target has *none* of the picker items / target is missing *at least one* of them). Integer values are preserved (2 and 3), so existing serialized `QuerySet` data deserializes unchanged.
10+
- Added XML documentation on `MatchType` values and the `Matches` method to surface the semantics in IntelliSense.
11+
12+
### Fixed
13+
- `CollectionItemQueryPropertyDrawer.IsCombinationImpossible` no longer reports false positives for `Any` + `NotAny` and `All` + `NotAll` rule pairs. Impossibility for these now requires the stricter *subset* relationship between pickers (not just any intersection), matching the real-world satisfiability. `All` + `NotAny` continues to be flagged on any intersection, which is correct.
714

815
## [2.5.1] - 06/04/2026
916
### Added

Scripts/Editor/PropertyDrawers/CollectionItemQueryPropertyDrawer.cs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ private bool IsMatchTypeValid(SerializedProperty queryProp, int elementIndex, in
257257

258258
int otherEnumIndex = otherMatchTypeProp.enumValueIndex;
259259

260-
if (IsCombinationImpossible(candidateEnumIndex, otherEnumIndex))
260+
if (IsCombinationImpossible(candidateEnumIndex, candidateItems, otherEnumIndex, otherItems))
261261
return false;
262262
}
263263

@@ -292,7 +292,7 @@ private bool HasImpossibleRules(SerializedProperty queryProp)
292292
continue;
293293
int matchB = matchTypeBProp.enumValueIndex;
294294

295-
if (IsCombinationImpossible(matchA, matchB))
295+
if (IsCombinationImpossible(matchA, itemsA, matchB, itemsB))
296296
return true;
297297
}
298298
}
@@ -451,31 +451,43 @@ private static bool HasItemIntersection(HashSet<(long, long)> a, HashSet<(long,
451451
return false;
452452
}
453453

454-
private static bool IsCombinationImpossible(int matchA, int matchB)
454+
private static bool IsCombinationImpossible(
455+
int matchA, HashSet<(long, long)> itemsA,
456+
int matchB, HashSet<(long, long)> itemsB)
455457
{
456-
// 0 = Any, 1 = All, 2 = SomeNot (forbids any), 3 = None (forbids all)
457-
// Only truly impossible when overlapping items can never satisfy both rules:
458-
// - All + SomeNot: must have all AND must have none → impossible
459-
// - Any + SomeNot: must have at least one AND must have none → impossible
460-
// - All + None: must have all AND must not have all → impossible
461-
// - Any + None: must have at least one AND must not have all → satisfiable (partial overlap ok)
462-
int lo = Mathf.Min(matchA, matchB);
463-
int hi = Mathf.Max(matchA, matchB);
464-
465-
// All(1) + SomeNot(2)
466-
if (lo == 1 && hi == 2)
458+
// Caller guarantees itemsA ∩ itemsB is non-empty.
459+
// 0 = Any, 1 = All, 2 = NotAny, 3 = NotAll.
460+
//
461+
// All + NotAny: the overlap is forced-in by All and forced-out by NotAny → always impossible.
462+
// Any(X) + NotAny(Y): impossible iff every X item is also forbidden by Y (X ⊆ Y); otherwise an X-only item satisfies both.
463+
// All(X) + NotAll(Y): impossible iff every Y item is already required by X (Y ⊆ X); otherwise target = X misses a Y-only item.
464+
// Any + NotAll and all other pairings remain satisfiable in the general (non-degenerate-picker) case.
465+
466+
if ((matchA == 1 && matchB == 2) || (matchA == 2 && matchB == 1))
467467
return true;
468468

469-
// Any(0) + SomeNot(2)
470-
if (lo == 0 && hi == 2)
471-
return true;
469+
if (matchA == 0 && matchB == 2) return IsSubsetOf(itemsA, itemsB);
470+
if (matchA == 2 && matchB == 0) return IsSubsetOf(itemsB, itemsA);
472471

473-
// All(1) + None(3)
474-
if (lo == 1 && hi == 3)
475-
return true;
472+
if (matchA == 1 && matchB == 3) return IsSubsetOf(itemsB, itemsA);
473+
if (matchA == 3 && matchB == 1) return IsSubsetOf(itemsA, itemsB);
476474

477475
return false;
478476
}
477+
478+
private static bool IsSubsetOf(HashSet<(long, long)> candidate, HashSet<(long, long)> container)
479+
{
480+
if (candidate == null || container == null || candidate.Count == 0)
481+
return false;
482+
483+
foreach ((long, long) item in candidate)
484+
{
485+
if (!container.Contains(item))
486+
return false;
487+
}
488+
489+
return true;
490+
}
479491
}
480492
}
481493

Scripts/Runtime/Core/CollectionItemQuery.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ public class CollectionItemQuery<T> where T : ScriptableObject, ISOCItem
1111
{
1212
public enum MatchType
1313
{
14+
/// <summary>Target has at least one of the picker items.</summary>
1415
Any = 0,
16+
/// <summary>Target has every one of the picker items.</summary>
1517
All = 1,
16-
SomeNot = 2,
17-
None = 3,
18+
/// <summary>Target has none of the picker items (all picker items are absent).</summary>
19+
NotAny = 2,
20+
/// <summary>Target is missing at least one of the picker items (not all are present).</summary>
21+
NotAll = 3,
1822
}
1923

2024
[Serializable]
@@ -63,6 +67,13 @@ public override string ToString()
6367
return stringBuilder.ToString();
6468
}
6569

70+
/// <summary>
71+
/// Evaluates every <see cref="QuerySet"/> in the query against <paramref name="targetItems"/>.
72+
/// Returns <c>true</c> only if every set passes its <see cref="MatchType"/> check;
73+
/// an empty query returns <c>true</c>.
74+
/// </summary>
75+
/// <param name="targetItems">The items to test against (e.g., the tags on a rigidbody).</param>
76+
/// <param name="resultMatchCount">Total number of individual picker items found across all query sets. Informational only; does not affect the return value.</param>
6677
public bool Matches(IEnumerable<T> targetItems, out int resultMatchCount)
6778
{
6879
targetGuids.Clear();
@@ -96,16 +107,16 @@ public bool Matches(IEnumerable<T> targetItems, out int resultMatchCount)
96107

97108
switch (qs.MatchType)
98109
{
99-
case MatchType.SomeNot:
110+
case MatchType.NotAny:
100111
{
101-
if (matchCount > 0)
112+
if (matchCount > 0)
102113
return false;
103114
break;
104115
}
105-
case MatchType.None:
116+
case MatchType.NotAll:
106117
{
107-
if (matchCount == pickerCount)
108-
return false;
118+
if (matchCount == pickerCount)
119+
return false;
109120
break;
110121
}
111122
case MatchType.Any:

0 commit comments

Comments
 (0)