Skip to content

Commit b80ef5a

Browse files
authored
Merge pull request #193 from brunomikoski/feature/new-bitewise-for-picker
Bitwise for Item Picker and Query
2 parents c7426fa + 07d34ae commit b80ef5a

13 files changed

Lines changed: 373 additions & 95 deletions

CHANGELOG.MD

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ All notable changes to this project will be documented in this file.
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).
66

7-
## [Unreleased]
7+
## [2.6.0] - 05/16/2026
88
### Added
9+
- New `PreviewMode` enum (`Default`/`Inline`/`PropertyEditorWindow`) on `SOCItemEditorOptionsAttribute`, plus a matching project-wide default in `SOCSettings` (Project Settings > Scriptable Object Collection > Item Inspector). When set to `PropertyEditorWindow`, the per-field Edit button opens Unity's floating Property Editor via `EditorUtility.OpenPropertyEditor` instead of the inline preview panel — fixes rendering issues with third-party inspectors (e.g. Odin) that don't draw correctly in the inline IMGUI drawer. `Default` on the attribute defers to the project setting; project setting defaults to `Inline` so existing behavior is unchanged.
10+
- New `CollectionItemMask64` static helper plus `IEnumerable<T>.ToItemMask64<T>()` and `List<T>.FromItemMask64<T>()` extension methods. Provides a 64-bit bitmask representation of a set of collection items keyed on `ScriptableObjectCollectionItem.Index`, with `Bit`/`Has`/`Add`/`Remove`/`Overlaps`/`IsSubsetOf` helpers and a `From<T>(items, out bool fits)` builder that flags when any item's `Index >= 64`. Bit positions are per-collection — callers are responsible for only comparing masks built from items of the same collection.
11+
- `CollectionItemPicker<T>` now exposes a cached bitmask (`CachedMask`) and a `CanUseBitmask` gate (combines `MaskFitsIn64` with every item collection's `SupportsBitmaskIndexing` flag), plus `CountMatchesIn(ulong)` and `CountMatchesIn(IEnumerable<T>)` overloads backed by a SWAR `PopCount`. The mask is rebuilt on `OnAfterDeserialize`, on every mutating operation (`Add`/`Clear`/`Remove`/`Insert`/`RemoveAt`/indexer set), and lazily in Edit Mode; reused without rebuild in Play Mode.
12+
- `ScriptableObjectCollection` virtual `SupportsBitmaskIndexing` (default `true`). Override to `false` on collections whose item ordering can change at runtime (e.g. CDN-delivered content) so the bitmask fast path is skipped — each item's `Index` is cached on first access and would otherwise go stale after a reorder.
913
- `CollectionItemPicker<T>.ToString()` override returning `[item1, item2, ...]` (empty collection renders as `[]`; null entries render as `<null>`). Uses `StringBuilder` to avoid per-call allocations on long pickers.
1014
- `CollectionItemQuery<T>.IsEmpty()` returns `true` when every `QuerySet`'s picker contains zero items — useful for skipping the `Matches` evaluation on default-constructed queries.
1115
- `CollectionItemPickerPropertyDrawer` now writes `itemLastKnownName` and `collectionLastKnownName` when an entry is added to a picker, so newly added items have populated cache names immediately instead of empty strings.
1216

1317
### Changed
1418
- 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.
1519
- Added XML documentation on `MatchType` values and the `Matches` method to surface the semantics in IntelliSense.
16-
- `CollectionItemPicker<T>` events renamed: `OnItemTypeAddedEvent``OnItemAddedEvent`, `OnItemTypeRemovedEvent``OnItemRemovedEvent`. **Breaking** for external subscribers — `+=`/`-=` call sites must be updated. `OnChangedEvent` is unchanged.
20+
- `CollectionItemQuery<T>.Matches` now selects between a bitmask fast path (`MatchesViaBitmask`) and the existing GUID-based path (`MatchesViaGuids`) at the start of each call. The bitmask path is used only when every `QuerySet`'s picker reports `CanUseBitmask == true`; otherwise the call falls back to the original GUID comparison. Match results are unchanged — only the evaluation strategy.
21+
- `CollectionItemPicker<T>` events renamed: `OnItemTypeAddedEvent``OnItemAddedEvent`, `OnItemTypeRemovedEvent``OnItemRemovedEvent`. The old names are preserved as `[Obsolete]` shims with explicit `add`/`remove` accessors that forward subscriptions to the new events, so existing `+=`/`-=` call sites keep compiling with a deprecation warning pointing to the new name. The shims will be removed in a future major version. `OnChangedEvent` is unchanged.
1722
- `CollectionItemIndirectReference.itemLastKnownName` and `collectionLastKnownName` are now wrapped in `#if UNITY_EDITOR` and no longer compiled into player builds. Runtime resolution relies exclusively on GUIDs.
1823
- `CollectionItemQuery<T>.Matches(IEnumerable<T> targetItems, out int)` now tolerates a `null` `targetItems` — treated as empty, so `Any`/`All` sets fail and `NotAny`/`NotAll` sets pass.
1924

@@ -24,6 +29,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2429

2530
### Removed
2631
- Removed the runtime fallback in `CollectionItemIndirectReference.TryGetCollectionItem` that, when the stored GUIDs failed to resolve, attempted to look the collection and item back up by `collectionLastKnownName`/`itemLastKnownName`. With the names now editor-only, GUID is the single runtime lookup path; rename recovery is handled at edit time by the drawer instead.
32+
- Removed long-deprecated `[Obsolete]` API surface that has been carrying compile warnings across prior releases:
33+
- `SOCSettings.SetDefaultNamespace(string)` — use `SetNamespacePrefix(string)` instead.
34+
- The four `ScriptableObjectCollectionUtility.CreateScriptableObjectOfType(...)` overloads that took a `parentFolder` / `parentFolderPath` plus a `createFolderForThisCollection` bool. Use the overloads that take a complete path; build the path with `Path.Combine` at the call site if you previously relied on the auto-folder behavior.
35+
- `DrawAsSOCItemAttribute` — no longer needed since Unity 2022.2 supports `PropertyDrawer` on interfaces directly.
36+
- `CollectionItemEditorOptions` class — use `SOCItemEditorOptionsAttribute` instead.
37+
- `CollectionsRegistry.GetCollectionByGUID(string)` — use the `LongGuid` overload (regenerate your collection's static access class to pick up the new GUID type).
38+
- `ScriptableObjectCollection.GetItemByGUID(string)` — use the `LongGuid` overload (same: regenerate the static access class).
2739

2840
## [2.5.1] - 06/04/2026
2941
### Added

Scripts/Editor/Core/CodeGenerationUtility.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,7 @@ public static void GenerateIndirectAccessForCollectionItemType(string collection
366366

367367
// Delete any existing files that have the old deprecated extension.
368368
string deprecatedFileName = targetFileName + ExtensionOld;
369-
#if UNITY_2023_1_OR_NEWER
370369
if (AssetDatabase.AssetPathExists(deprecatedFileName))
371-
#else
372-
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(deprecatedFileName) != null)
373-
#endif
374370
{
375371
Debug.LogWarning($"Deleting deprecated Indirect Access file '{deprecatedFileName}'.");
376372
AssetDatabase.DeleteAsset(deprecatedFileName);

Scripts/Editor/Core/SOCSettings.cs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ public static SOCSettings Instance
6767
[SerializeField]
6868
internal string generatedScriptsDefaultFilePath = @"Assets\Generated\Scripts";
6969

70+
[SerializeField]
71+
private PreviewMode defaultPreviewMode = PreviewMode.Inline;
72+
public PreviewMode DefaultPreviewMode =>
73+
defaultPreviewMode == PreviewMode.Default ? PreviewMode.Inline : defaultPreviewMode;
74+
7075
private static readonly GUIContent namespacePrefixGUIContent = new GUIContent(
7176
"Prefix",
7277
"When using the Create New Collection wizard," +
@@ -77,14 +82,6 @@ public static SOCSettings Instance
7782
"If specified, automatically derived namespaces will only include up to this many folders inside your " +
7883
"project's Scripts folder.");
7984

80-
81-
[Obsolete("Default Namespace has been renamed to Namespace Prefix. Please use the corresponding function.")]
82-
public void SetDefaultNamespace(string namespacePrefix)
83-
{
84-
SetNamespacePrefix(namespacePrefix);
85-
Save();
86-
}
87-
8885
public void SetNamespacePrefix(string namespacePrefix)
8986
{
9087
this.namespacePrefix = namespacePrefix;
@@ -130,6 +127,28 @@ public void OnSceneGUI(string search)
130127
}
131128
}
132129

130+
EditorGUILayout.LabelField("Item Inspector", EditorStyles.boldLabel);
131+
using (EditorGUI.ChangeCheckScope changeCheck = new EditorGUI.ChangeCheckScope())
132+
{
133+
PreviewMode resolvedMode = DefaultPreviewMode;
134+
PreviewMode newMode = (PreviewMode)EditorGUILayout.EnumPopup(
135+
new GUIContent(
136+
"Default Preview Mode",
137+
"How the Edit button on a collection item field reveals the item.\n" +
138+
"Inline draws the item's properties beneath the field.\n" +
139+
"Property Editor Window opens Unity's floating inspector — useful when " +
140+
"third-party inspectors (e.g. Odin) don't render correctly inline.\n" +
141+
"Per-field [SOCItemEditorOptions(PreviewMode = ...)] overrides this."),
142+
resolvedMode,
143+
value => (PreviewMode)value != PreviewMode.Default,
144+
false);
145+
if (changeCheck.changed)
146+
{
147+
defaultPreviewMode = newMode;
148+
Save();
149+
}
150+
}
151+
133152
EditorGUILayout.LabelField("Default Generated Scripts Folder", EditorStyles.boldLabel);
134153
using (EditorGUI.ChangeCheckScope changeCheck = new EditorGUI.ChangeCheckScope())
135154
{
@@ -157,6 +176,12 @@ public void SetGeneratedScriptsDefaultFilePath(string assetPath)
157176
Save();
158177
}
159178

179+
public void SetDefaultPreviewMode(PreviewMode previewMode)
180+
{
181+
defaultPreviewMode = previewMode == PreviewMode.Default ? PreviewMode.Inline : previewMode;
182+
Save();
183+
}
184+
160185
public void Save()
161186
{
162187
string json = EditorJsonUtility.ToJson(this);

Scripts/Editor/PropertyDrawers/CollectionItemPropertyDrawer.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99

1010
namespace BrunoMikoski.ScriptableObjectCollections
1111
{
12-
#if UNITY_2022_2_OR_NEWER
1312
[CustomPropertyDrawer(typeof(ISOCItem), true)]
14-
#endif
1513
[CustomPropertyDrawer(typeof(ScriptableObjectCollectionItem), true)]
1614
public class CollectionItemPropertyDrawer : PropertyDrawer
1715
{
@@ -331,17 +329,29 @@ private void DrawEditFoldoutButton(ref Rect popupRect, ScriptableObject targetIt
331329
popupRect.width -= buttonRect.width;
332330
buttonRect.x += popupRect.width;
333331

334-
GUIContent guiContent = CollectionEditorGUI.EditGUIContent;
332+
if (ResolvePreviewMode() == PreviewMode.PropertyEditorWindow)
333+
{
334+
if (GUI.Button(buttonRect, CollectionEditorGUI.EditGUIContent))
335+
EditorUtility.OpenPropertyEditor(targetItem);
336+
return;
337+
}
335338

336-
if (showingItemPreview)
337-
guiContent = CollectionEditorGUI.CloseGUIContent;
339+
GUIContent guiContent = showingItemPreview
340+
? CollectionEditorGUI.CloseGUIContent
341+
: CollectionEditorGUI.EditGUIContent;
338342

339343
if (GUI.Button(buttonRect, guiContent))
340344
{
341345
showingItemPreview = !showingItemPreview;
342346
}
343347
}
344348

349+
private PreviewMode ResolvePreviewMode()
350+
{
351+
PreviewMode mode = OptionsAttribute.PreviewMode;
352+
return mode == PreviewMode.Default ? SOCSettings.Instance.DefaultPreviewMode : mode;
353+
}
354+
345355
public void OverrideFieldInfo(FieldInfo targetFieldInfo)
346356
{
347357
overrideFieldInfo = targetFieldInfo;

Scripts/Editor/Utils/ScriptableObjectCollectionUtility.cs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,6 @@ public static T CreateScriptableObjectOfType<T>(string path, string name) where
1919
return CreateScriptableObjectOfType(typeof(T), path, name) as T;
2020
}
2121

22-
[Obsolete("Please use the overload that contains a complete path.")]
23-
public static T CreateScriptableObjectOfType<T>(DefaultAsset parentFolder, bool createFolderForThisCollection,
24-
string targetName) where T : ScriptableObject
25-
{
26-
return CreateScriptableObjectOfType<T>(AssetDatabase.GetAssetPath(parentFolder),
27-
createFolderForThisCollection, targetName);
28-
}
29-
30-
[Obsolete("Please use the overload that contains a complete path.")]
31-
public static T CreateScriptableObjectOfType<T>(string parentFolderPath, bool createFolderForThisCollection,
32-
string targetName) where T : ScriptableObject
33-
{
34-
if (createFolderForThisCollection)
35-
parentFolderPath = Path.Combine(parentFolderPath, $"{targetName}");
36-
37-
return CreateScriptableObjectOfType<T>(parentFolderPath, targetName);
38-
}
39-
4022
public static ScriptableObject CreateScriptableObjectOfType(Type targetType, string path, string name)
4123
{
4224
ScriptableObject targetCollection = ScriptableObject.CreateInstance(targetType);
@@ -49,25 +31,6 @@ public static ScriptableObject CreateScriptableObjectOfType(Type targetType, str
4931
return targetCollection;
5032
}
5133

52-
[Obsolete("Please use the overload that contains a complete path.")]
53-
public static ScriptableObject CreateScriptableObjectOfType(Type targetType, string parentFolderPath,
54-
bool createFolderForThisCollection, string targetName)
55-
{
56-
if (createFolderForThisCollection)
57-
parentFolderPath = Path.Combine(parentFolderPath, $"{targetName}");
58-
59-
return CreateScriptableObjectOfType(targetType, parentFolderPath, targetName);
60-
}
61-
62-
[Obsolete("Please use the overload that contains a complete path.")]
63-
public static ScriptableObject CreateScriptableObjectOfType(
64-
Type targetType, DefaultAsset parentFolder, bool createFoldForThisCollection, string collectionName)
65-
{
66-
return CreateScriptableObjectOfType(
67-
targetType, AssetDatabase.GetAssetPath(parentFolder),
68-
createFoldForThisCollection, collectionName);
69-
}
70-
7134
public static void GoToItem(ISOCItem socItem)
7235
{
7336
SessionState.SetInt(COLLECTION_CUSTOM_EDITOR_GO_TO_ITEM_INDEX_KEY, socItem.Collection.IndexOf(socItem));

Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,24 @@ public enum LabelMode
1515
NoLabel = 1,
1616
//LabelOnSeparateLine = 2, // TODO
1717
}
18-
19-
#if UNITY_2022_2_OR_NEWER
20-
[Obsolete("DrawAsSOCItemAttribute is not needed anymore, since Unity 2022 PropertyDrawers can be applied to interfaces")]
21-
#endif
22-
[AttributeUsage(AttributeTargets.Field)]
23-
public class DrawAsSOCItemAttribute : PropertyAttribute
18+
19+
public enum PreviewMode
2420
{
25-
21+
/// <summary>
22+
/// Defer to the project-wide default set in Project Settings &gt; Scriptable Object Collection.
23+
/// </summary>
24+
Default = 0,
25+
/// <summary>
26+
/// Draw the item's properties as an inline panel beneath the field.
27+
/// </summary>
28+
Inline = 1,
29+
/// <summary>
30+
/// Open Unity's floating Property Editor window for the item. Useful when third-party
31+
/// inspectors (e.g. Odin) don't render correctly in the inline drawer.
32+
/// </summary>
33+
PropertyEditorWindow = 2,
2634
}
2735

28-
2936
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class)]
3037
public class SOCItemEditorOptionsAttribute : Attribute
3138
{
@@ -35,6 +42,12 @@ public class SOCItemEditorOptionsAttribute : Attribute
3542

3643
public bool ShouldDrawPreviewButton { get; set; } = true;
3744

45+
/// <summary>
46+
/// Controls how the preview button reveals the item. <see cref="PreviewMode.Default"/>
47+
/// falls back to the project-wide setting in <c>SOCSettings</c>.
48+
/// </summary>
49+
public PreviewMode PreviewMode { get; set; } = PreviewMode.Default;
50+
3851
public string ValidateMethod { get; set; }
3952

4053
/// <summary>
@@ -50,10 +63,4 @@ public class SOCItemEditorOptionsAttribute : Attribute
5063
/// </summary>
5164
public string OnSelectCallbackMethod { get; set; }
5265
}
53-
54-
//Temporary
55-
[Obsolete("CollectionItemEditorOptions is deprecated, please use SOCItemEditorOptionsAttribute instead.")]
56-
public class CollectionItemEditorOptions : SOCItemEditorOptionsAttribute
57-
{
58-
}
5966
}

0 commit comments

Comments
 (0)