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,10 +3,16 @@ 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.Utils;
9
12
  using static RelationalComponentProcessor;
13
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
14
+ using Unity.Profiling;
15
+ #endif
10
16
 
11
17
  /// <summary>
12
18
  /// Automatically assigns parent components (components up the transform hierarchy) to the decorated field.
@@ -86,6 +92,15 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
86
92
  FieldMetadata<ParentComponentAttribute>[]
87
93
  > FieldsByType = new();
88
94
 
95
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
96
+ private static readonly ProfilerMarker ParentFastPathMarker = new ProfilerMarker(
97
+ "RelationalComponents.Parent.FastPath"
98
+ );
99
+ private static readonly ProfilerMarker ParentFallbackMarker = new ProfilerMarker(
100
+ "RelationalComponents.Parent.Fallback"
101
+ );
102
+ #endif
103
+
89
104
  /// <summary>
90
105
  /// Assigns fields on <paramref name="component"/> marked with <see cref="ParentComponentAttribute"/>.
91
106
  /// </summary>
@@ -104,11 +119,22 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
104
119
  /// </example>
105
120
  public static void AssignParentComponents(this Component component)
106
121
  {
107
- Type componentType = component.GetType();
108
122
  FieldMetadata<ParentComponentAttribute>[] fields = FieldsByType.GetOrAdd(
109
- componentType,
123
+ component.GetType(),
110
124
  type => GetFieldMetadata<ParentComponentAttribute>(type)
111
125
  );
126
+ AssignParentComponents(component, fields);
127
+ }
128
+
129
+ internal static void AssignParentComponents(
130
+ Component component,
131
+ FieldMetadata<ParentComponentAttribute>[] fields
132
+ )
133
+ {
134
+ if (component == null || fields == null || fields.Length == 0)
135
+ {
136
+ return;
137
+ }
112
138
 
113
139
  foreach (FieldMetadata<ParentComponentAttribute> field in fields)
114
140
  {
@@ -117,8 +143,7 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
117
143
  continue;
118
144
  }
119
145
 
120
- bool foundParent;
121
- FilterParameters filters = new(field.attribute);
146
+ FilterParameters filters = field.Filters;
122
147
  Transform root = component.transform;
123
148
  if (field.attribute.OnlyAncestors)
124
149
  {
@@ -128,128 +153,382 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
128
153
  if (root == null)
129
154
  {
130
155
  SetEmptyCollection(component, field);
131
- foundParent = false;
156
+ LogMissingComponentError(component, field, "parent");
157
+ continue;
132
158
  }
133
159
  else
134
160
  {
135
- switch (field.kind)
161
+ bool foundParent;
162
+ if (field.kind == FieldKind.Single)
136
163
  {
137
- case FieldKind.Array:
138
- {
139
- using PooledResource<List<Component>> parentComponentBuffer =
140
- Buffers<Component>.List.Get(out List<Component> parentComponents);
141
- GetParentComponents(
164
+ if (
165
+ TryAssignParentSingleFast(
142
166
  root,
167
+ field,
168
+ filters,
169
+ out Component parentComponent
170
+ )
171
+ || TryGetFirstParentComponent(
172
+ root,
173
+ filters,
143
174
  field.elementType,
144
175
  field.attribute,
145
176
  field.isInterface,
146
- parentComponents
147
- );
177
+ out parentComponent
178
+ )
179
+ )
180
+ {
181
+ field.SetValue(component, parentComponent);
182
+ foundParent = true;
183
+ }
184
+ else
185
+ {
186
+ foundParent = false;
187
+ }
188
+ }
189
+ else
190
+ {
191
+ switch (field.kind)
192
+ {
193
+ case FieldKind.Array:
194
+ {
195
+ if (
196
+ TryAssignParentCollectionFast(
197
+ component,
198
+ root,
199
+ field,
200
+ filters,
201
+ out bool assignedAny
202
+ )
203
+ )
204
+ {
205
+ foundParent = assignedAny;
206
+ break;
207
+ }
148
208
 
149
- int filteredCount = FilterComponentsInPlace(
150
- parentComponents,
151
- filters,
152
- field.attribute,
153
- field.elementType,
154
- field.isInterface,
155
- filterDisabledComponents: false
156
- );
209
+ using PooledResource<List<Component>> parentComponentBuffer =
210
+ Buffers<Component>.List.Get(
211
+ out List<Component> parentComponents
212
+ );
213
+ GetParentComponents(
214
+ root,
215
+ field.elementType,
216
+ field.attribute,
217
+ field.isInterface,
218
+ parentComponents
219
+ );
157
220
 
158
- Array correctTypedArray = field.arrayCreator(filteredCount);
159
- for (int i = 0; i < filteredCount; ++i)
160
- {
161
- correctTypedArray.SetValue(parentComponents[i], i);
221
+ int filteredCount =
222
+ !filters.RequiresPostProcessing && field.attribute.MaxCount <= 0
223
+ ? parentComponents.Count
224
+ : FilterComponentsInPlace(
225
+ parentComponents,
226
+ filters,
227
+ field.attribute,
228
+ field.elementType,
229
+ field.isInterface,
230
+ filterDisabledComponents: false
231
+ );
232
+
233
+ Array correctTypedArray = field.arrayCreator(filteredCount);
234
+ for (int i = 0; i < filteredCount; ++i)
235
+ {
236
+ correctTypedArray.SetValue(parentComponents[i], i);
237
+ }
238
+
239
+ field.SetValue(component, correctTypedArray);
240
+ foundParent = filteredCount > 0;
241
+ break;
162
242
  }
243
+ case FieldKind.List:
244
+ {
245
+ if (
246
+ TryAssignParentCollectionFast(
247
+ component,
248
+ root,
249
+ field,
250
+ filters,
251
+ out bool assignedAny
252
+ )
253
+ )
254
+ {
255
+ foundParent = assignedAny;
256
+ break;
257
+ }
163
258
 
164
- field.setter(component, correctTypedArray);
165
- foundParent = filteredCount > 0;
166
- break;
167
- }
168
- case FieldKind.List:
169
- {
170
- using PooledResource<List<Component>> parentComponentBuffer =
171
- Buffers<Component>.List.Get(out List<Component> parentComponents);
172
- GetParentComponents(
173
- root,
174
- field.elementType,
175
- field.attribute,
176
- field.isInterface,
177
- parentComponents
178
- );
259
+ using PooledResource<List<Component>> parentComponentBuffer =
260
+ Buffers<Component>.List.Get(
261
+ out List<Component> parentComponents
262
+ );
263
+ GetParentComponents(
264
+ root,
265
+ field.elementType,
266
+ field.attribute,
267
+ field.isInterface,
268
+ parentComponents
269
+ );
179
270
 
180
- int filteredCount = FilterComponentsInPlace(
181
- parentComponents,
182
- filters,
183
- field.attribute,
184
- field.elementType,
185
- field.isInterface,
186
- filterDisabledComponents: false
187
- );
271
+ int filteredCount =
272
+ !filters.RequiresPostProcessing && field.attribute.MaxCount <= 0
273
+ ? parentComponents.Count
274
+ : FilterComponentsInPlace(
275
+ parentComponents,
276
+ filters,
277
+ field.attribute,
278
+ field.elementType,
279
+ field.isInterface,
280
+ filterDisabledComponents: false
281
+ );
188
282
 
189
- IList instance = field.listCreator(filteredCount);
190
- for (int i = 0; i < filteredCount; ++i)
191
- {
192
- instance.Add(parentComponents[i]);
283
+ if (field.GetValue(component) is IList instance)
284
+ {
285
+ instance.Clear();
286
+ }
287
+ else
288
+ {
289
+ instance = field.listCreator(filteredCount);
290
+ field.SetValue(component, instance);
291
+ }
292
+
293
+ for (int i = 0; i < filteredCount; ++i)
294
+ {
295
+ instance.Add(parentComponents[i]);
296
+ }
297
+
298
+ foundParent = filteredCount > 0;
299
+ break;
193
300
  }
301
+ case FieldKind.HashSet:
302
+ {
303
+ if (
304
+ TryAssignParentCollectionFast(
305
+ component,
306
+ root,
307
+ field,
308
+ filters,
309
+ out bool assignedAny
310
+ )
311
+ )
312
+ {
313
+ foundParent = assignedAny;
314
+ break;
315
+ }
194
316
 
195
- field.setter(component, instance);
196
- foundParent = filteredCount > 0;
197
- break;
198
- }
199
- case FieldKind.HashSet:
200
- {
201
- using PooledResource<List<Component>> parentComponentBuffer =
202
- Buffers<Component>.List.Get(out List<Component> parentComponents);
203
- GetParentComponents(
204
- root,
205
- field.elementType,
206
- field.attribute,
207
- field.isInterface,
208
- parentComponents
209
- );
317
+ using PooledResource<List<Component>> parentComponentBuffer =
318
+ Buffers<Component>.List.Get(
319
+ out List<Component> parentComponents
320
+ );
321
+ GetParentComponents(
322
+ root,
323
+ field.elementType,
324
+ field.attribute,
325
+ field.isInterface,
326
+ parentComponents
327
+ );
210
328
 
211
- int filteredCount = FilterComponentsInPlace(
212
- parentComponents,
213
- filters,
214
- field.attribute,
215
- field.elementType,
216
- field.isInterface,
217
- filterDisabledComponents: false
218
- );
329
+ int filteredCount = FilterComponentsInPlace(
330
+ parentComponents,
331
+ filters,
332
+ field.attribute,
333
+ field.elementType,
334
+ field.isInterface,
335
+ filterDisabledComponents: false
336
+ );
219
337
 
220
- object instance = field.hashSetCreator(filteredCount);
221
- for (int i = 0; i < filteredCount; ++i)
222
- {
223
- field.hashSetAdder(instance, parentComponents[i]);
224
- }
338
+ object instance = field.GetValue(component);
339
+ if (instance != null && field.hashSetClearer != null)
340
+ {
341
+ field.hashSetClearer(instance);
342
+ }
343
+ else
344
+ {
345
+ instance = field.hashSetCreator(filteredCount);
346
+ field.SetValue(component, instance);
347
+ }
225
348
 
226
- field.setter(component, instance);
227
- foundParent = filteredCount > 0;
228
- break;
229
- }
230
- default:
231
- {
232
- foundParent = TryGetFirstParentComponent(
233
- root,
234
- field.elementType,
235
- field.attribute,
236
- field.isInterface,
237
- out Component parentComponent
238
- );
349
+ for (int i = 0; i < filteredCount; ++i)
350
+ {
351
+ field.hashSetAdder(instance, parentComponents[i]);
352
+ }
239
353
 
240
- if (foundParent)
354
+ foundParent = filteredCount > 0;
355
+ break;
356
+ }
357
+ default:
241
358
  {
242
- field.setter(component, parentComponent);
359
+ foundParent = false;
360
+ break;
243
361
  }
244
-
245
- break;
246
362
  }
247
363
  }
364
+
365
+ if (!foundParent)
366
+ {
367
+ LogMissingComponentError(component, field, "parent");
368
+ }
369
+ }
370
+ }
371
+ }
372
+
373
+ internal static FieldMetadata<ParentComponentAttribute>[] GetOrCreateFields(Type type)
374
+ {
375
+ return FieldsByType.GetOrAdd(type, t => GetFieldMetadata<ParentComponentAttribute>(t));
376
+ }
377
+
378
+ private static bool TryAssignParentCollectionFast(
379
+ Component component,
380
+ Transform root,
381
+ FieldMetadata<ParentComponentAttribute> metadata,
382
+ FilterParameters filters,
383
+ out bool assignedAny
384
+ )
385
+ {
386
+ assignedAny = false;
387
+ ParentComponentAttribute attribute = metadata.attribute;
388
+ if (
389
+ metadata.isInterface
390
+ || filters.RequiresPostProcessing
391
+ || attribute.MaxDepth > 0
392
+ || root == null
393
+ )
394
+ {
395
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
396
+ ParentFallbackMarker.Begin();
397
+ ParentFallbackMarker.End();
398
+ #endif
399
+ return false;
400
+ }
401
+
402
+ #if UNITY_EDITOR && UNITY_2020_2_OR_NEWER
403
+ using (ParentFastPathMarker.Auto())
404
+ #endif
405
+ {
406
+ Array parents = ParentComponentFastInvoker.GetArray(
407
+ root,
408
+ metadata.elementType,
409
+ attribute.IncludeInactive
410
+ );
411
+
412
+ Array filtered = FilterParentArray(metadata, parents);
413
+ assignedAny = AssignParentComponentsFromArray(component, metadata, filtered);
414
+ return true;
415
+ }
416
+ }
417
+
418
+ private static Array FilterParentArray(
419
+ FieldMetadata<ParentComponentAttribute> metadata,
420
+ Array source
421
+ )
422
+ {
423
+ Type elementType = metadata.elementType;
424
+ if (source == null || source.Length == 0)
425
+ {
426
+ return Array.CreateInstance(elementType, 0);
427
+ }
428
+
429
+ int maxCount = metadata.attribute.MaxCount;
430
+ if (maxCount <= 0)
431
+ {
432
+ return source;
433
+ }
434
+
435
+ int limit = Math.Min(maxCount, source.Length);
436
+ Array staged = Array.CreateInstance(elementType, limit);
437
+ int writeIndex = 0;
438
+
439
+ for (int i = 0; i < source.Length && writeIndex < limit; ++i)
440
+ {
441
+ Component candidate = source.GetValue(i) as Component;
442
+ if (candidate == null)
443
+ {
444
+ continue;
445
+ }
446
+
447
+ staged.SetValue(candidate, writeIndex++);
448
+ }
449
+
450
+ if (writeIndex == staged.Length)
451
+ {
452
+ return staged;
453
+ }
454
+
455
+ Array result = Array.CreateInstance(elementType, writeIndex);
456
+ if (writeIndex > 0)
457
+ {
458
+ Array.Copy(staged, 0, result, 0, writeIndex);
459
+ }
460
+
461
+ return result;
462
+ }
463
+
464
+ private static bool AssignParentComponentsFromArray(
465
+ Component component,
466
+ FieldMetadata<ParentComponentAttribute> metadata,
467
+ Array parents
468
+ )
469
+ {
470
+ if (parents == null)
471
+ {
472
+ parents = Array.CreateInstance(metadata.elementType, 0);
473
+ }
474
+
475
+ int count = parents.Length;
476
+
477
+ switch (metadata.kind)
478
+ {
479
+ case FieldKind.Array:
480
+ {
481
+ Array instance = metadata.arrayCreator(count);
482
+ for (int i = 0; i < count; ++i)
483
+ {
484
+ instance.SetValue(parents.GetValue(i), i);
485
+ }
486
+
487
+ metadata.SetValue(component, instance);
488
+ return count > 0;
248
489
  }
490
+ case FieldKind.List:
491
+ {
492
+ if (metadata.GetValue(component) is IList list)
493
+ {
494
+ list.Clear();
495
+ }
496
+ else
497
+ {
498
+ list = metadata.listCreator(count);
499
+ metadata.SetValue(component, list);
500
+ }
501
+
502
+ for (int i = 0; i < count; ++i)
503
+ {
504
+ list.Add(parents.GetValue(i));
505
+ }
249
506
 
250
- if (!foundParent)
507
+ return count > 0;
508
+ }
509
+ case FieldKind.HashSet:
251
510
  {
252
- LogMissingComponentError(component, field, "parent");
511
+ object hashSet = metadata.GetValue(component);
512
+ if (hashSet != null && metadata.hashSetClearer != null)
513
+ {
514
+ metadata.hashSetClearer(hashSet);
515
+ }
516
+ else
517
+ {
518
+ hashSet = metadata.hashSetCreator(count);
519
+ metadata.SetValue(component, hashSet);
520
+ }
521
+
522
+ for (int i = 0; i < count; ++i)
523
+ {
524
+ metadata.hashSetAdder(hashSet, parents.GetValue(i));
525
+ }
526
+
527
+ return count > 0;
528
+ }
529
+ default:
530
+ {
531
+ return false;
253
532
  }
254
533
  }
255
534
  }
@@ -318,22 +597,57 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
318
597
  return buffer;
319
598
  }
320
599
 
600
+ private static bool TryAssignParentSingleFast(
601
+ Transform root,
602
+ FieldMetadata<ParentComponentAttribute> metadata,
603
+ FilterParameters filters,
604
+ out Component parentComponent
605
+ )
606
+ {
607
+ parentComponent = null;
608
+
609
+ if (
610
+ root == null
611
+ || metadata.isInterface
612
+ || filters.RequiresPostProcessing
613
+ || metadata.attribute.IncludeInactive
614
+ || metadata.attribute.MaxDepth > 0
615
+ )
616
+ {
617
+ return false;
618
+ }
619
+
620
+ Component candidate = root.GetComponentInParent(metadata.elementType);
621
+ if (candidate == null)
622
+ {
623
+ return false;
624
+ }
625
+
626
+ parentComponent = candidate;
627
+ return true;
628
+ }
629
+
321
630
  private static bool TryGetFirstParentComponent(
322
631
  Transform root,
632
+ FilterParameters filters,
323
633
  Type elementType,
324
634
  ParentComponentAttribute attribute,
325
635
  bool isInterface,
326
636
  out Component result
327
637
  )
328
638
  {
329
- FilterParameters filters = new(attribute);
330
639
  Transform current = root;
331
640
  int depth = 0;
332
641
  int maxDepth = attribute.MaxDepth > 0 ? attribute.MaxDepth : int.MaxValue;
333
642
 
334
- using PooledResource<List<Component>> scratch = Buffers<Component>.List.Get(
335
- out List<Component> components
336
- );
643
+ bool needsScratch = isInterface || filters.RequiresPostProcessing;
644
+ List<Component> components = null;
645
+ PooledResource<List<Component>> scratch = default;
646
+ if (needsScratch)
647
+ {
648
+ scratch = Buffers<Component>.List.Get(out components);
649
+ }
650
+
337
651
  while (current != null && depth < maxDepth)
338
652
  {
339
653
  if (
@@ -349,6 +663,10 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
349
663
  )
350
664
  )
351
665
  {
666
+ if (needsScratch)
667
+ {
668
+ scratch.Dispose();
669
+ }
352
670
  result = resolved;
353
671
  return true;
354
672
  }
@@ -357,6 +675,11 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
357
675
  depth++;
358
676
  }
359
677
 
678
+ if (needsScratch)
679
+ {
680
+ scratch.Dispose();
681
+ }
682
+
360
683
  result = null;
361
684
  return false;
362
685
  }
@@ -373,4 +696,55 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
373
696
  return current == target ? depth : int.MaxValue;
374
697
  }
375
698
  }
699
+
700
+ internal static class ParentComponentFastInvoker
701
+ {
702
+ private static readonly Dictionary<Type, Func<Component, bool, Array>> ArrayGetters = new();
703
+
704
+ private static readonly MethodInfo GetComponentsInParentGeneric = typeof(Component)
705
+ .GetMethods(BindingFlags.Instance | BindingFlags.Public)
706
+ .First(method =>
707
+ method.Name == nameof(Component.GetComponentsInParent)
708
+ && method.IsGenericMethodDefinition
709
+ && method.GetParameters().Length == 1
710
+ && method.GetParameters()[0].ParameterType == typeof(bool)
711
+ );
712
+
713
+ internal static Array GetArray(Component component, Type elementType, bool includeInactive)
714
+ {
715
+ if (!ArrayGetters.TryGetValue(elementType, out Func<Component, bool, Array> getter))
716
+ {
717
+ getter = CreateArrayGetter(elementType);
718
+ ArrayGetters[elementType] = getter;
719
+ }
720
+
721
+ return getter(component, includeInactive);
722
+ }
723
+
724
+ private static Func<Component, bool, Array> CreateArrayGetter(Type elementType)
725
+ {
726
+ MethodInfo closedMethod = GetComponentsInParentGeneric.MakeGenericMethod(elementType);
727
+ ParameterExpression componentParameter = Expression.Parameter(
728
+ typeof(Component),
729
+ "component"
730
+ );
731
+ ParameterExpression includeInactiveParameter = Expression.Parameter(
732
+ typeof(bool),
733
+ "includeInactive"
734
+ );
735
+ MethodCallExpression invoke = Expression.Call(
736
+ componentParameter,
737
+ closedMethod,
738
+ includeInactiveParameter
739
+ );
740
+ UnaryExpression convert = Expression.Convert(invoke, typeof(Array));
741
+ return Expression
742
+ .Lambda<Func<Component, bool, Array>>(
743
+ convert,
744
+ componentParameter,
745
+ includeInactiveParameter
746
+ )
747
+ .Compile();
748
+ }
749
+ }
376
750
  }