com.wallstop-studios.unity-helpers 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +1 -0
  2. package/Docs/ILIST_SORTING_PERFORMANCE.md +16 -16
  3. package/Docs/INDEX.md +1 -0
  4. package/Docs/RANDOM_PERFORMANCE.md +15 -15
  5. package/Docs/REFLECTION_HELPERS.md +84 -1
  6. package/Docs/REFLECTION_PERFORMANCE.md +169 -0
  7. package/{package-lock.json.meta → Docs/REFLECTION_PERFORMANCE.md.meta} +1 -1
  8. package/Docs/RELATIONAL_COMPONENTS.md +6 -0
  9. package/Docs/RELATIONAL_COMPONENT_PERFORMANCE.md +63 -0
  10. package/Docs/RELATIONAL_COMPONENT_PERFORMANCE.md.meta +7 -0
  11. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  12. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  13. package/Editor/Sprites/AnimationCopier.cs +1 -1
  14. package/Editor/Sprites/AnimationViewerWindow.cs +4 -4
  15. package/Editor/Sprites/SpriteSettingsApplierAPI.cs +2 -1
  16. package/Editor/Sprites/TextureResizerWizard.cs +4 -3
  17. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +3 -3
  18. package/README.md +8 -3
  19. package/Runtime/Core/Attributes/BaseRelationalComponentAttribute.cs +147 -20
  20. package/Runtime/Core/Attributes/ChildComponentAttribute.cs +630 -117
  21. package/Runtime/Core/Attributes/NotNullAttribute.cs +5 -2
  22. package/Runtime/Core/Attributes/ParentComponentAttribute.cs +477 -103
  23. package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +26 -3
  24. package/Runtime/Core/Attributes/RelationalComponentExtensions.cs +19 -3
  25. package/Runtime/Core/Attributes/SiblingComponentAttribute.cs +265 -92
  26. package/Runtime/Core/CodeGen.meta +8 -0
  27. package/Runtime/Core/DataStructure/ImmutableBitSet.cs +5 -20
  28. package/Runtime/Core/Helper/Logging/UnityLogTagFormatter.cs +11 -7
  29. package/Runtime/Core/Helper/Objects.cs +1 -1
  30. package/Runtime/Core/Helper/ReflectionHelpers.Factory.cs +5142 -0
  31. package/Runtime/Core/Helper/ReflectionHelpers.Factory.cs.meta +11 -0
  32. package/Runtime/Core/Helper/ReflectionHelpers.cs +1812 -1518
  33. package/Runtime/Core/Math/Line2D.cs +2 -4
  34. package/Runtime/Core/Math/Line3D.cs +2 -4
  35. package/Runtime/Core/Random/FlurryBurstRandom.cs +0 -6
  36. package/Runtime/Tags/AttributeMetadataCache.cs +4 -6
  37. package/Runtime/Tags/CosmeticEffectData.cs +1 -1
  38. package/Runtime/Visuals/UIToolkit/MultiFileSelectorElement.cs +3 -3
  39. package/Tests/Editor/Helper/HelpersTests.cs +2 -2
  40. package/Tests/Editor/Helper/ReflectionHelpersTypedEditorTests.cs +87 -0
  41. package/Tests/Editor/Helper/ReflectionHelpersTypedEditorTests.cs.meta +11 -0
  42. package/Tests/Editor/Helper/SpriteHelpersTests.cs +1 -1
  43. package/Tests/Editor/PrefabCheckerReportTests.cs +3 -3
  44. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +18 -12
  45. package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +8 -7
  46. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +2 -1
  47. package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +6 -5
  48. package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +2 -1
  49. package/Tests/Editor/Sprites/SpriteCropperTests.cs +7 -6
  50. package/Tests/Editor/Sprites/SpritePivotAdjusterAdditionalTests.cs +2 -1
  51. package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +4 -3
  52. package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +10 -9
  53. package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +2 -1
  54. package/Tests/Runtime/Helper/ObjectsTests.cs +1 -1
  55. package/Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests.cs +2923 -0
  56. package/Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests.cs.meta +11 -0
  57. package/Tests/Runtime/Helper/ReflectionHelperTests.cs +660 -0
  58. package/Tests/Runtime/Performance/ReflectionPerformanceTests.cs +1238 -0
  59. package/Tests/Runtime/Performance/ReflectionPerformanceTests.cs.meta +11 -0
  60. package/Tests/Runtime/Performance/RelationalComponentBenchmarkTests.cs +832 -0
  61. package/Tests/Runtime/Performance/RelationalComponentBenchmarkTests.cs.meta +11 -0
  62. package/package.json +1 -1
  63. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs +0 -60
  64. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs.meta +0 -3
@@ -3,11 +3,17 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
3
3
  using System;
4
4
  using System.Collections;
5
5
  using System.Collections.Generic;
6
+ using System.Linq;
7
+ using System.Linq.Expressions;
8
+ using System.Reflection;
6
9
  using Extension;
7
10
  using UnityEngine;
8
11
  using WallstopStudios.UnityHelpers.Core.Helper;
9
12
  using WallstopStudios.UnityHelpers.Utils;
10
13
  using static RelationalComponentProcessor;
14
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
15
+ using Unity.Profiling;
16
+ #endif
11
17
 
12
18
  /// <summary>
13
19
  /// Automatically assigns child components (components down the transform hierarchy) to the decorated field.
@@ -82,6 +88,15 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
82
88
  FieldMetadata<ChildComponentAttribute>[]
83
89
  > FieldsByType = new();
84
90
 
91
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
92
+ private static readonly ProfilerMarker ChildFastPathMarker = new ProfilerMarker(
93
+ "RelationalComponents.Child.FastPath"
94
+ );
95
+ private static readonly ProfilerMarker ChildFallbackMarker = new ProfilerMarker(
96
+ "RelationalComponents.Child.Fallback"
97
+ );
98
+ #endif
99
+
85
100
  /// <summary>
86
101
  /// Assigns fields on <paramref name="component"/> marked with <see cref="ChildComponentAttribute"/>.
87
102
  /// </summary>
@@ -100,11 +115,22 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
100
115
  /// </example>
101
116
  public static void AssignChildComponents(this Component component)
102
117
  {
103
- Type componentType = component.GetType();
104
118
  FieldMetadata<ChildComponentAttribute>[] fields = FieldsByType.GetOrAdd(
105
- componentType,
119
+ component.GetType(),
106
120
  type => GetFieldMetadata<ChildComponentAttribute>(type)
107
121
  );
122
+ AssignChildComponents(component, fields);
123
+ }
124
+
125
+ internal static void AssignChildComponents(
126
+ Component component,
127
+ FieldMetadata<ChildComponentAttribute>[] fields
128
+ )
129
+ {
130
+ if (component == null || fields == null || fields.Length == 0)
131
+ {
132
+ return;
133
+ }
108
134
 
109
135
  foreach (FieldMetadata<ChildComponentAttribute> metadata in fields)
110
136
  {
@@ -113,159 +139,575 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
113
139
  continue;
114
140
  }
115
141
 
116
- bool foundChild;
117
- FilterParameters filters = new(metadata.attribute);
118
-
119
- using PooledResource<List<Transform>> childBufferResource =
120
- Buffers<Transform>.List.Get();
121
- List<Transform> childBuffer = childBufferResource.resource;
122
-
123
- switch (metadata.kind)
142
+ bool foundChild = false;
143
+ if (metadata.kind == FieldKind.Single)
124
144
  {
125
- case FieldKind.Array:
145
+ if (TryAssignChildSingleFast(component, metadata, out Component childComponent))
126
146
  {
127
- using PooledResource<List<Component>> cacheResource =
128
- Buffers<Component>.List.Get();
129
- List<Component> cache = cacheResource.resource;
130
-
131
- CollectChildComponents(component, metadata, childBuffer, cache);
132
-
133
- int filteredCount = FilterComponentsInPlace(
134
- cache,
135
- filters,
136
- metadata.attribute,
137
- metadata.elementType,
138
- metadata.isInterface
139
- );
140
-
141
- Array correctTypedArray = metadata.arrayCreator(filteredCount);
142
- for (int i = 0; i < filteredCount; ++i)
147
+ metadata.SetValue(component, childComponent);
148
+ foundChild = true;
149
+ }
150
+ else
151
+ {
152
+ FilterParameters filters = metadata.Filters;
153
+ using PooledResource<List<Transform>> childBufferResource =
154
+ Buffers<Transform>.List.Get(out List<Transform> childBuffer);
155
+ if (
156
+ TryAssignChildSingleFallback(
157
+ component,
158
+ metadata,
159
+ filters,
160
+ childBuffer,
161
+ out childComponent
162
+ )
163
+ )
143
164
  {
144
- correctTypedArray.SetValue(cache[i], i);
165
+ metadata.SetValue(component, childComponent);
166
+ foundChild = true;
145
167
  }
146
-
147
- metadata.setter(component, correctTypedArray);
148
- foundChild = filteredCount > 0;
149
- break;
150
168
  }
151
- case FieldKind.List:
169
+ }
170
+ else
171
+ {
172
+ FilterParameters filters = metadata.Filters;
173
+ if (
174
+ TryAssignChildCollectionFast(
175
+ component,
176
+ metadata,
177
+ filters,
178
+ out bool assignedAny
179
+ )
180
+ )
181
+ {
182
+ foundChild = assignedAny;
183
+ }
184
+ else
152
185
  {
153
- using PooledResource<List<Component>> cacheResource =
154
- Buffers<Component>.List.Get();
155
- List<Component> cache = cacheResource.resource;
186
+ using PooledResource<List<Transform>> childBufferResource =
187
+ Buffers<Transform>.List.Get(out List<Transform> childBuffer);
188
+
189
+ switch (metadata.kind)
190
+ {
191
+ case FieldKind.Array:
192
+ {
193
+ using PooledResource<List<Component>> cacheResource =
194
+ Buffers<Component>.List.Get(out List<Component> cache);
195
+ cache.Clear();
196
+ int filteredCount = EnumerateFilteredChildComponents(
197
+ component,
198
+ metadata,
199
+ filters,
200
+ childBuffer,
201
+ candidate =>
202
+ {
203
+ cache.Add(candidate);
204
+ return true;
205
+ }
206
+ );
156
207
 
157
- CollectChildComponents(component, metadata, childBuffer, cache);
208
+ Array correctTypedArray = metadata.arrayCreator(filteredCount);
209
+ for (int i = 0; i < filteredCount; ++i)
210
+ {
211
+ correctTypedArray.SetValue(cache[i], i);
212
+ }
158
213
 
159
- int filteredCount = FilterComponentsInPlace(
160
- cache,
161
- filters,
162
- metadata.attribute,
163
- metadata.elementType,
164
- metadata.isInterface
165
- );
214
+ metadata.SetValue(component, correctTypedArray);
215
+ foundChild = filteredCount > 0;
216
+ break;
217
+ }
218
+ case FieldKind.List:
219
+ {
220
+ object existing = metadata.GetValue(component);
221
+ IList list = existing as IList;
222
+ if (list == null)
223
+ {
224
+ int initialCapacity =
225
+ metadata.attribute.MaxCount > 0
226
+ ? metadata.attribute.MaxCount
227
+ : 0;
228
+ list = metadata.listCreator(initialCapacity);
229
+ metadata.SetValue(component, list);
230
+ }
231
+ else
232
+ {
233
+ list.Clear();
234
+ }
166
235
 
167
- IList instance = metadata.listCreator(filteredCount);
168
- for (int i = 0; i < filteredCount; ++i)
169
- {
170
- instance.Add(cache[i]);
236
+ int added = EnumerateFilteredChildComponents(
237
+ component,
238
+ metadata,
239
+ filters,
240
+ childBuffer,
241
+ candidate =>
242
+ {
243
+ list.Add(candidate);
244
+ return true;
245
+ }
246
+ );
247
+
248
+ foundChild = added > 0;
249
+ break;
250
+ }
251
+ case FieldKind.HashSet:
252
+ {
253
+ object instance = metadata.GetValue(component);
254
+ if (instance != null && metadata.hashSetClearer != null)
255
+ {
256
+ metadata.hashSetClearer(instance);
257
+ }
258
+ else
259
+ {
260
+ int initialCapacity =
261
+ metadata.attribute.MaxCount > 0
262
+ ? metadata.attribute.MaxCount
263
+ : 0;
264
+ instance = metadata.hashSetCreator(initialCapacity);
265
+ metadata.SetValue(component, instance);
266
+ }
267
+
268
+ int added = EnumerateFilteredChildComponents(
269
+ component,
270
+ metadata,
271
+ filters,
272
+ childBuffer,
273
+ candidate =>
274
+ {
275
+ metadata.hashSetAdder(instance, candidate);
276
+ return true;
277
+ }
278
+ );
279
+
280
+ foundChild = added > 0;
281
+ break;
282
+ }
283
+ default:
284
+ {
285
+ break;
286
+ }
171
287
  }
288
+ }
289
+ }
172
290
 
173
- metadata.setter(component, instance);
174
- foundChild = filteredCount > 0;
175
- break;
291
+ if (!foundChild)
292
+ {
293
+ LogMissingComponentError(component, metadata, "child");
294
+ }
295
+ }
296
+ }
297
+
298
+ internal static FieldMetadata<ChildComponentAttribute>[] GetOrCreateFields(Type type)
299
+ {
300
+ return FieldsByType.GetOrAdd(type, t => GetFieldMetadata<ChildComponentAttribute>(t));
301
+ }
302
+
303
+ private static bool TryAssignChildSingleFast(
304
+ Component component,
305
+ FieldMetadata<ChildComponentAttribute> metadata,
306
+ out Component childComponent
307
+ )
308
+ {
309
+ childComponent = null;
310
+ ChildComponentAttribute attribute = metadata.attribute;
311
+
312
+ if (
313
+ metadata.isInterface
314
+ || attribute.MaxDepth != 0
315
+ || attribute.TagFilter != null
316
+ || attribute.NameFilter != null
317
+ )
318
+ {
319
+ return false;
320
+ }
321
+
322
+ Component[] results = component.GetComponentsInChildren(
323
+ metadata.elementType,
324
+ attribute.IncludeInactive
325
+ );
326
+
327
+ if (results == null || results.Length == 0)
328
+ {
329
+ return false;
330
+ }
331
+
332
+ Transform componentTransform = component.transform;
333
+ for (int i = 0; i < results.Length; ++i)
334
+ {
335
+ Component candidate = results[i];
336
+ if (candidate == null)
337
+ {
338
+ continue;
339
+ }
340
+
341
+ if (attribute.OnlyDescendants && candidate.transform == componentTransform)
342
+ {
343
+ continue;
344
+ }
345
+
346
+ childComponent = candidate;
347
+ return true;
348
+ }
349
+
350
+ return false;
351
+ }
352
+
353
+ private static bool TryAssignChildCollectionFast(
354
+ Component component,
355
+ FieldMetadata<ChildComponentAttribute> metadata,
356
+ FilterParameters filters,
357
+ out bool assignedAny
358
+ )
359
+ {
360
+ assignedAny = false;
361
+ ChildComponentAttribute attribute = metadata.attribute;
362
+ if (metadata.isInterface || filters.RequiresPostProcessing || attribute.MaxDepth > 0)
363
+ {
364
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
365
+ ChildFallbackMarker.Begin();
366
+ ChildFallbackMarker.End();
367
+ #endif
368
+ return false;
369
+ }
370
+
371
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
372
+ using (ChildFastPathMarker.Auto())
373
+ #endif
374
+ {
375
+ Array children = ChildComponentFastInvoker.GetArray(
376
+ component,
377
+ metadata.elementType,
378
+ attribute.IncludeInactive
379
+ );
380
+
381
+ Array filtered = FilterChildArray(component, metadata, children);
382
+ Array ordered = EnsureBreadthFirstOrder(component, metadata, filtered);
383
+ assignedAny = AssignChildComponentsFromArray(component, metadata, ordered);
384
+ return true;
385
+ }
386
+ }
387
+
388
+ private static Array FilterChildArray(
389
+ Component component,
390
+ FieldMetadata<ChildComponentAttribute> metadata,
391
+ Array source
392
+ )
393
+ {
394
+ Type elementType = metadata.elementType;
395
+ if (source == null || source.Length == 0)
396
+ {
397
+ return Array.CreateInstance(elementType, 0);
398
+ }
399
+
400
+ ChildComponentAttribute attribute = metadata.attribute;
401
+ bool onlyDescendants = attribute.OnlyDescendants;
402
+ Transform self = component.transform;
403
+
404
+ int maxCount = attribute.MaxCount;
405
+ if (!onlyDescendants && maxCount <= 0)
406
+ {
407
+ return source;
408
+ }
409
+
410
+ int limit = maxCount > 0 ? Math.Min(maxCount, source.Length) : source.Length;
411
+ Array staged = Array.CreateInstance(elementType, limit);
412
+ int writeIndex = 0;
413
+
414
+ for (int i = 0; i < source.Length && writeIndex < limit; ++i)
415
+ {
416
+ Component candidate = source.GetValue(i) as Component;
417
+ if (candidate == null)
418
+ {
419
+ continue;
420
+ }
421
+
422
+ if (onlyDescendants && candidate.transform == self)
423
+ {
424
+ continue;
425
+ }
426
+
427
+ staged.SetValue(candidate, writeIndex++);
428
+ }
429
+
430
+ if (writeIndex == staged.Length)
431
+ {
432
+ return staged;
433
+ }
434
+
435
+ Array result = Array.CreateInstance(elementType, writeIndex);
436
+ if (writeIndex > 0)
437
+ {
438
+ Array.Copy(staged, 0, result, 0, writeIndex);
439
+ }
440
+
441
+ return result;
442
+ }
443
+
444
+ private static Array EnsureBreadthFirstOrder(
445
+ Component component,
446
+ FieldMetadata<ChildComponentAttribute> metadata,
447
+ Array source
448
+ )
449
+ {
450
+ Type elementType = metadata.elementType;
451
+ if (source == null)
452
+ {
453
+ return Array.CreateInstance(elementType, 0);
454
+ }
455
+
456
+ int length = source.Length;
457
+ if (length <= 1)
458
+ {
459
+ return source;
460
+ }
461
+
462
+ ChildComponentAttribute attribute = metadata.attribute;
463
+ using PooledResource<List<Transform>> traversalResource = Buffers<Transform>.List.Get(
464
+ out List<Transform> traversal
465
+ );
466
+ component.IterateOverAllChildrenRecursivelyBreadthFirst(
467
+ traversal,
468
+ includeSelf: !attribute.OnlyDescendants,
469
+ attribute.MaxDepth
470
+ );
471
+
472
+ using PooledResource<Dictionary<Transform, List<Component>>> groupedResource =
473
+ DictionaryBuffer<Transform, List<Component>>.Dictionary.Get(
474
+ out Dictionary<Transform, List<Component>> grouped
475
+ );
476
+ using PooledResource<Dictionary<Transform, int>> positionsResource = DictionaryBuffer<
477
+ Transform,
478
+ int
479
+ >.Dictionary.Get(out Dictionary<Transform, int> positions);
480
+
481
+ for (int i = 0; i < length; ++i)
482
+ {
483
+ Component candidate = source.GetValue(i) as Component;
484
+ if (candidate == null)
485
+ {
486
+ continue;
487
+ }
488
+
489
+ Transform key = candidate.transform;
490
+ if (!grouped.TryGetValue(key, out List<Component> list))
491
+ {
492
+ list = new List<Component>();
493
+ grouped.Add(key, list);
494
+ positions.Add(key, 0);
495
+ }
496
+
497
+ list.Add(candidate);
498
+ }
499
+
500
+ Array ordered = Array.CreateInstance(elementType, length);
501
+ int writeIndex = 0;
502
+
503
+ for (int i = 0; i < traversal.Count && writeIndex < length; ++i)
504
+ {
505
+ Transform transform = traversal[i];
506
+ if (!grouped.TryGetValue(transform, out List<Component> list))
507
+ {
508
+ continue;
509
+ }
510
+
511
+ int position = positions[transform];
512
+ while (position < list.Count && writeIndex < length)
513
+ {
514
+ ordered.SetValue(list[position], writeIndex++);
515
+ position++;
516
+ }
517
+
518
+ if (position >= list.Count)
519
+ {
520
+ grouped.Remove(transform);
521
+ positions.Remove(transform);
522
+ }
523
+ else
524
+ {
525
+ positions[transform] = position;
526
+ }
527
+ }
528
+
529
+ if (writeIndex < length && grouped.Count > 0)
530
+ {
531
+ foreach (KeyValuePair<Transform, List<Component>> pair in grouped)
532
+ {
533
+ List<Component> list = pair.Value;
534
+ int position = positions[pair.Key];
535
+ while (position < list.Count && writeIndex < length)
536
+ {
537
+ ordered.SetValue(list[position], writeIndex++);
538
+ position++;
176
539
  }
177
- case FieldKind.HashSet:
540
+ if (writeIndex >= length)
178
541
  {
179
- using PooledResource<List<Component>> cacheResource =
180
- Buffers<Component>.List.Get();
181
- List<Component> cache = cacheResource.resource;
542
+ break;
543
+ }
544
+ }
545
+ }
182
546
 
183
- CollectChildComponents(component, metadata, childBuffer, cache);
547
+ if (writeIndex >= length)
548
+ {
549
+ return ordered;
550
+ }
184
551
 
185
- int filteredCount = FilterComponentsInPlace(
186
- cache,
187
- filters,
188
- metadata.attribute,
189
- metadata.elementType,
190
- metadata.isInterface
191
- );
552
+ if (writeIndex == 0)
553
+ {
554
+ return Array.CreateInstance(elementType, 0);
555
+ }
192
556
 
193
- object instance = metadata.hashSetCreator(filteredCount);
194
- for (int i = 0; i < filteredCount; ++i)
195
- {
196
- metadata.hashSetAdder(instance, cache[i]);
197
- }
557
+ Array trimmed = Array.CreateInstance(elementType, writeIndex);
558
+ Array.Copy(ordered, 0, trimmed, 0, writeIndex);
559
+ return trimmed;
560
+ }
198
561
 
199
- metadata.setter(component, instance);
200
- foundChild = filteredCount > 0;
201
- break;
562
+ private static bool AssignChildComponentsFromArray(
563
+ Component component,
564
+ FieldMetadata<ChildComponentAttribute> metadata,
565
+ Array componentsArray
566
+ )
567
+ {
568
+ if (componentsArray == null)
569
+ {
570
+ componentsArray = Array.CreateInstance(metadata.elementType, 0);
571
+ }
572
+
573
+ int count = componentsArray.Length;
574
+
575
+ switch (metadata.kind)
576
+ {
577
+ case FieldKind.Array:
578
+ {
579
+ Array instance = metadata.arrayCreator(count);
580
+ for (int i = 0; i < count; ++i)
581
+ {
582
+ instance.SetValue(componentsArray.GetValue(i), i);
202
583
  }
203
- default:
584
+
585
+ metadata.SetValue(component, instance);
586
+ return count > 0;
587
+ }
588
+ case FieldKind.List:
589
+ {
590
+ if (metadata.GetValue(component) is IList list)
204
591
  {
205
- foundChild = false;
206
- Component childComponent = null;
592
+ list.Clear();
593
+ }
594
+ else
595
+ {
596
+ list = metadata.listCreator(count);
597
+ metadata.SetValue(component, list);
598
+ }
207
599
 
208
- using PooledResource<List<Component>> scratch = Buffers<Component>.List.Get(
209
- out List<Component> components
210
- );
600
+ for (int i = 0; i < count; ++i)
601
+ {
602
+ list.Add(componentsArray.GetValue(i));
603
+ }
211
604
 
212
- foreach (
213
- Transform child in component.IterateOverAllChildrenRecursivelyBreadthFirst(
214
- childBuffer,
215
- includeSelf: !metadata.attribute.OnlyDescendants,
216
- maxDepth: metadata.attribute.MaxDepth
217
- )
218
- )
219
- {
220
- if (
221
- TryResolveSingleComponent(
222
- child,
223
- filters,
224
- metadata.elementType,
225
- metadata.isInterface,
226
- metadata.attribute.AllowInterfaces,
227
- components,
228
- out Component resolved
229
- )
230
- )
231
- {
232
- childComponent = resolved;
233
- foundChild = true;
234
- break;
235
- }
236
- }
605
+ return count > 0;
606
+ }
607
+ case FieldKind.HashSet:
608
+ {
609
+ object hashSet = metadata.GetValue(component);
610
+ if (hashSet != null && metadata.hashSetClearer != null)
611
+ {
612
+ metadata.hashSetClearer(hashSet);
613
+ }
614
+ else
615
+ {
616
+ hashSet = metadata.hashSetCreator(count);
617
+ metadata.SetValue(component, hashSet);
618
+ }
237
619
 
238
- if (foundChild)
239
- {
240
- metadata.setter(component, childComponent);
241
- }
242
- break;
620
+ for (int i = 0; i < count; ++i)
621
+ {
622
+ metadata.hashSetAdder(hashSet, componentsArray.GetValue(i));
243
623
  }
624
+
625
+ return count > 0;
626
+ }
627
+ default:
628
+ {
629
+ return false;
244
630
  }
631
+ }
632
+ }
245
633
 
246
- if (!foundChild)
634
+ private static bool TryAssignChildSingleFallback(
635
+ Component component,
636
+ FieldMetadata<ChildComponentAttribute> metadata,
637
+ FilterParameters filters,
638
+ List<Transform> childBuffer,
639
+ out Component childComponent
640
+ )
641
+ {
642
+ bool needsScratch = metadata.isInterface || filters.RequiresPostProcessing;
643
+ List<Component> scratchList = null;
644
+ PooledResource<List<Component>> scratch = default;
645
+ if (needsScratch)
646
+ {
647
+ scratch = Buffers<Component>.List.Get(out scratchList);
648
+ }
649
+
650
+ childComponent = null;
651
+
652
+ foreach (
653
+ Transform child in component.IterateOverAllChildrenRecursivelyBreadthFirst(
654
+ childBuffer,
655
+ includeSelf: !metadata.attribute.OnlyDescendants,
656
+ maxDepth: metadata.attribute.MaxDepth
657
+ )
658
+ )
659
+ {
660
+ if (
661
+ TryResolveSingleComponent(
662
+ child,
663
+ filters,
664
+ metadata.elementType,
665
+ metadata.isInterface,
666
+ metadata.attribute.AllowInterfaces,
667
+ scratchList,
668
+ out Component resolved
669
+ )
670
+ )
247
671
  {
248
- LogMissingComponentError(component, metadata, "child");
672
+ childComponent = resolved;
673
+ break;
249
674
  }
250
675
  }
676
+
677
+ if (needsScratch)
678
+ {
679
+ scratch.Dispose();
680
+ }
681
+
682
+ return childComponent != null;
251
683
  }
252
684
 
253
- private static List<Component> CollectChildComponents(
685
+ private static int EnumerateFilteredChildComponents(
254
686
  Component component,
255
687
  FieldMetadata<ChildComponentAttribute> metadata,
688
+ FilterParameters filters,
256
689
  List<Transform> childBuffer,
257
- List<Component> cache
690
+ Func<Component, bool> onComponent
258
691
  )
259
692
  {
260
- cache.Clear();
693
+ if (component == null)
694
+ {
695
+ return 0;
696
+ }
697
+
698
+ ChildComponentAttribute attribute = metadata.attribute;
699
+ int maxAssignments = attribute.MaxCount > 0 ? attribute.MaxCount : int.MaxValue;
700
+ int added = 0;
701
+
261
702
  using PooledResource<List<Component>> componentBuffer = Buffers<Component>.List.Get(
262
703
  out List<Component> components
263
704
  );
705
+
264
706
  foreach (
265
707
  Transform child in component.IterateOverAllChildrenRecursivelyBreadthFirst(
266
708
  childBuffer,
267
- includeSelf: !metadata.attribute.OnlyDescendants,
268
- maxDepth: metadata.attribute.MaxDepth
709
+ includeSelf: !attribute.OnlyDescendants,
710
+ maxDepth: attribute.MaxDepth
269
711
  )
270
712
  )
271
713
  {
@@ -273,12 +715,83 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
273
715
  child,
274
716
  metadata.elementType,
275
717
  metadata.isInterface,
276
- metadata.attribute.AllowInterfaces,
718
+ attribute.AllowInterfaces,
277
719
  components
278
720
  );
279
- cache.AddRange(components);
721
+
722
+ for (int i = 0; i < components.Count; ++i)
723
+ {
724
+ Component candidate = components[i];
725
+ if (!PassesStateAndFilters(candidate, filters, filterDisabledComponents: true))
726
+ {
727
+ continue;
728
+ }
729
+
730
+ if (!onComponent(candidate))
731
+ {
732
+ return added;
733
+ }
734
+
735
+ added++;
736
+ if (added >= maxAssignments)
737
+ {
738
+ return added;
739
+ }
740
+ }
741
+ }
742
+
743
+ return added;
744
+ }
745
+ }
746
+
747
+ internal static class ChildComponentFastInvoker
748
+ {
749
+ private static readonly Dictionary<Type, Func<Component, bool, Array>> ArrayGetters = new();
750
+
751
+ private static readonly MethodInfo GetComponentsInChildrenGeneric = typeof(Component)
752
+ .GetMethods(BindingFlags.Instance | BindingFlags.Public)
753
+ .First(method =>
754
+ method.Name == nameof(Component.GetComponentsInChildren)
755
+ && method.IsGenericMethodDefinition
756
+ && method.GetParameters().Length == 1
757
+ && method.GetParameters()[0].ParameterType == typeof(bool)
758
+ );
759
+
760
+ internal static Array GetArray(Component component, Type elementType, bool includeInactive)
761
+ {
762
+ if (!ArrayGetters.TryGetValue(elementType, out Func<Component, bool, Array> getter))
763
+ {
764
+ getter = CreateArrayGetter(elementType);
765
+ ArrayGetters[elementType] = getter;
280
766
  }
281
- return cache;
767
+
768
+ return getter(component, includeInactive);
769
+ }
770
+
771
+ private static Func<Component, bool, Array> CreateArrayGetter(Type elementType)
772
+ {
773
+ MethodInfo closedMethod = GetComponentsInChildrenGeneric.MakeGenericMethod(elementType);
774
+ ParameterExpression componentParameter = Expression.Parameter(
775
+ typeof(Component),
776
+ "component"
777
+ );
778
+ ParameterExpression includeInactiveParameter = Expression.Parameter(
779
+ typeof(bool),
780
+ "includeInactive"
781
+ );
782
+ MethodCallExpression invoke = Expression.Call(
783
+ componentParameter,
784
+ closedMethod,
785
+ includeInactiveParameter
786
+ );
787
+ UnaryExpression convert = Expression.Convert(invoke, typeof(Array));
788
+ return Expression
789
+ .Lambda<Func<Component, bool, Array>>(
790
+ convert,
791
+ componentParameter,
792
+ includeInactiveParameter
793
+ )
794
+ .Compile();
282
795
  }
283
796
  }
284
797
  }