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
package/AGENTS.md CHANGED
@@ -26,6 +26,7 @@
26
26
  - Do not use underscores in function names, especially test function names.
27
27
  - Do not use regions, anywhere, ever.
28
28
  - Avoid `var` wherever possible, use expressive types.
29
+ - Do not use nullable reference types.
29
30
 
30
31
  ## Testing Guidelines
31
32
 
@@ -31,7 +31,7 @@ Run the `IListSortingPerformanceTests.Benchmark` test inside Unity’s Test Runn
31
31
 
32
32
  <!-- ILIST_SORT_WINDOWS_START -->
33
33
 
34
- _Last updated 2025-10-26 18:00 UTC on Windows 11 (10.0.26200) 64bit_
34
+ _Last updated 2025-11-01 02:07 UTC on Windows 11 (10.0.26200) 64bit_
35
35
 
36
36
  Times are single-pass measurements in milliseconds (lower is better). `n/a` indicates the algorithm was skipped for the dataset size.
37
37
 
@@ -39,31 +39,31 @@ Times are single-pass measurements in milliseconds (lower is better). `n/a` indi
39
39
 
40
40
  | List Size | Ghost | Meteor | Pattern-Defeating QuickSort | Grail | Power | Insertion |
41
41
  | --------- | -------- | -------- | --------------------------- | -------- | -------- | --------- |
42
- | 100 | 0.373 ms | 0.072 ms | 0.422 ms | 2.04 ms | 0.907 ms | 0.071 ms |
43
- | 1,000 | 0.021 ms | 0.025 ms | 0.007 ms | 0.040 ms | 0.005 ms | 0.005 ms |
44
- | 10,000 | 0.288 ms | 0.368 ms | 0.067 ms | 0.555 ms | 0.041 ms | 0.054 ms |
45
- | 100,000 | 3.35 ms | 4.81 ms | 0.703 ms | 10.7 ms | 0.424 ms | n/a |
46
- | 1,000,000 | 37.0 ms | 57.1 ms | 6.70 ms | 11.4 ms | 4.08 ms | n/a |
42
+ | 100 | 0.180 ms | 0.072 ms | 0.285 ms | 0.113 ms | 0.393 ms | 0.067 ms |
43
+ | 1,000 | 0.025 ms | 0.029 ms | 0.008 ms | 0.027 ms | 0.005 ms | 0.006 ms |
44
+ | 10,000 | 0.307 ms | 0.414 ms | 0.071 ms | 0.122 ms | 0.043 ms | 0.063 ms |
45
+ | 100,000 | 3.70 ms | 5.42 ms | 0.711 ms | 1.20 ms | 0.433 ms | n/a |
46
+ | 1,000,000 | 43.4 ms | 67.0 ms | 7.14 ms | 14.5 ms | 4.36 ms | n/a |
47
47
 
48
48
  ### Nearly Sorted (2% swaps)
49
49
 
50
50
  | List Size | Ghost | Meteor | Pattern-Defeating QuickSort | Grail | Power | Insertion |
51
51
  | --------- | -------- | -------- | --------------------------- | -------- | -------- | --------- |
52
- | 100 | 0.002 ms | 0.002 ms | 0.007 ms | 0.145 ms | 0.053 ms | 0.001 ms |
53
- | 1,000 | 0.021 ms | 0.025 ms | 0.032 ms | 0.009 ms | 0.020 ms | 0.006 ms |
54
- | 10,000 | 0.279 ms | 0.355 ms | 0.383 ms | 0.072 ms | 0.223 ms | 0.057 ms |
55
- | 100,000 | 3.17 ms | 4.64 ms | 4.61 ms | 0.689 ms | 3.47 ms | n/a |
56
- | 1,000,000 | 37.3 ms | 57.3 ms | 53.9 ms | 7.45 ms | 44.8 ms | n/a |
52
+ | 100 | 0.003 ms | 0.002 ms | 0.009 ms | 0.154 ms | 0.021 ms | 0.001 ms |
53
+ | 1,000 | 0.025 ms | 0.029 ms | 0.034 ms | 0.010 ms | 0.017 ms | 0.007 ms |
54
+ | 10,000 | 0.309 ms | 0.426 ms | 0.411 ms | 0.075 ms | 0.272 ms | 0.068 ms |
55
+ | 100,000 | 3.74 ms | 5.47 ms | 5.00 ms | 0.768 ms | 3.97 ms | n/a |
56
+ | 1,000,000 | 43.7 ms | 67.3 ms | 58.2 ms | 7.83 ms | 50.8 ms | n/a |
57
57
 
58
58
  ### Shuffled (deterministic)
59
59
 
60
60
  | List Size | Ghost | Meteor | Pattern-Defeating QuickSort | Grail | Power | Insertion |
61
61
  | --------- | -------- | -------- | --------------------------- | -------- | -------- | --------- |
62
- | 100 | 0.009 ms | 0.007 ms | 0.007 ms | 0.011 ms | 0.010 ms | 0.015 ms |
63
- | 1,000 | 0.143 ms | 0.123 ms | 0.089 ms | 0.106 ms | 0.111 ms | 1.34 ms |
64
- | 10,000 | 1.97 ms | 1.76 ms | 1.16 ms | 1.38 ms | 1.41 ms | 131 ms |
65
- | 100,000 | 28.3 ms | 23.5 ms | 14.4 ms | 17.3 ms | 17.5 ms | n/a |
66
- | 1,000,000 | 395 ms | 296 ms | 171 ms | 207 ms | 212 ms | n/a |
62
+ | 100 | 0.010 ms | 0.008 ms | 0.007 ms | 0.013 ms | 0.011 ms | 0.017 ms |
63
+ | 1,000 | 0.157 ms | 0.135 ms | 0.094 ms | 0.114 ms | 0.118 ms | 1.52 ms |
64
+ | 10,000 | 2.22 ms | 1.94 ms | 1.21 ms | 1.46 ms | 1.51 ms | 150 ms |
65
+ | 100,000 | 31.6 ms | 25.7 ms | 15.1 ms | 18.3 ms | 18.5 ms | n/a |
66
+ | 1,000,000 | 443 ms | 323 ms | 179 ms | 219 ms | 222 ms | n/a |
67
67
 
68
68
  <!-- ILIST_SORT_WINDOWS_END -->
69
69
 
package/Docs/INDEX.md CHANGED
@@ -329,6 +329,7 @@ Alphabetical index of all Unity Helpers features with quick links to documentati
329
329
 
330
330
  **Relational Components** - Auto-wire hierarchy components
331
331
  → [Relational Components Guide](RELATIONAL_COMPONENTS.md) | [README](../README.md#component-attributes)
332
+ → [Relational Component Performance Benchmarks](RELATIONAL_COMPONENT_PERFORMANCE.md)
332
333
 
333
334
  **RTree2D** - 2D R-tree for bounding boxes
334
335
  → [2D Spatial Trees](SPATIAL_TREES_2D_GUIDE.md) | [2D Performance](SPATIAL_TREE_2D_PERFORMANCE.md)
@@ -81,21 +81,21 @@ This document contains performance benchmarks for the various random number gene
81
81
 
82
82
  | Random | NextBool | Next | NextUInt | NextFloat | NextDouble | NextUint - Range | NextInt - Range |
83
83
  | --------------------------- | ----------- | ----------- | ------------- | ----------- | ----------- | ---------------- | --------------- |
84
- | DotNetRandom | 535,000,000 | 54,400,000 | 56,700,000 | 45,200,000 | 28,200,000 | 52,200,000 | 51,800,000 |
85
- | LinearCongruentialGenerator | 798,300,000 | 823,200,000 | 1,329,100,000 | 179,900,000 | 402,000,000 | 577,800,000 | 493,300,000 |
86
- | IllusionFlow | 778,000,000 | 662,100,000 | 895,100,000 | 178,000,000 | 331,100,000 | 444,000,000 | 384,900,000 |
87
- | PcgRandom | 762,500,000 | 668,400,000 | 892,700,000 | 179,700,000 | 345,200,000 | 450,000,000 | 400,200,000 |
88
- | RomuDuo | 758,600,000 | 579,300,000 | 767,300,000 | 167,200,000 | 255,900,000 | 446,500,000 | 397,400,000 |
89
- | SplitMix64 | 800,900,000 | 670,400,000 | 943,700,000 | 179,000,000 | 346,600,000 | 473,300,000 | 432,800,000 |
90
- | FlurryBurstRandom | 762,800,000 | 603,800,000 | 863,700,000 | 183,000,000 | 305,200,000 | 456,400,000 | 412,400,000 |
91
- | SquirrelRandom | 759,700,000 | 393,600,000 | 413,500,000 | 172,300,000 | 187,800,000 | 329,600,000 | 307,100,000 |
92
- | SystemRandom | 138,400,000 | 144,300,000 | 63,200,000 | 127,600,000 | 135,800,000 | 59,600,000 | 60,400,000 |
93
- | UnityRandom | 655,300,000 | 85,000,000 | 87,800,000 | 62,200,000 | 41,500,000 | 81,500,000 | 82,400,000 |
94
- | WyRandom | 758,600,000 | 390,600,000 | 457,100,000 | 166,800,000 | 191,100,000 | 293,600,000 | 274,700,000 |
95
- | XorShiftRandom | 766,300,000 | 554,600,000 | 586,100,000 | 181,100,000 | 259,100,000 | 443,300,000 | 393,600,000 |
96
- | XoroShiroRandom | 766,200,000 | 522,900,000 | 714,100,000 | 167,200,000 | 243,300,000 | 428,400,000 | 381,000,000 |
97
- | PhotonSpinRandom | 677,900,000 | 232,100,000 | 258,000,000 | 116,900,000 | 114,800,000 | 209,700,000 | 201,100,000 |
98
- | StormDropRandom | 758,100,000 | 538,100,000 | 698,600,000 | 184,100,000 | 271,800,000 | 406,300,000 | 365,900,000 |
84
+ | DotNetRandom | 543,400,000 | 53,000,000 | 58,000,000 | 44,900,000 | 27,100,000 | 53,700,000 | 53,700,000 |
85
+ | LinearCongruentialGenerator | 807,000,000 | 489,000,000 | 1,328,100,000 | 172,500,000 | 333,300,000 | 593,300,000 | 507,300,000 |
86
+ | IllusionFlow | 791,100,000 | 489,600,000 | 894,000,000 | 172,400,000 | 331,900,000 | 446,100,000 | 395,300,000 |
87
+ | PcgRandom | 796,800,000 | 538,000,000 | 914,300,000 | 176,800,000 | 344,900,000 | 452,000,000 | 406,200,000 |
88
+ | RomuDuo | 778,400,000 | 336,400,000 | 767,400,000 | 148,300,000 | 206,900,000 | 446,100,000 | 398,200,000 |
89
+ | SplitMix64 | 779,900,000 | 489,600,000 | 1,063,600,000 | 172,300,000 | 332,900,000 | 483,200,000 | 442,000,000 |
90
+ | FlurryBurstRandom | 778,500,000 | 487,000,000 | 951,900,000 | 172,100,000 | 335,300,000 | 456,000,000 | 410,600,000 |
91
+ | SquirrelRandom | 756,400,000 | 355,100,000 | 409,300,000 | 151,400,000 | 200,900,000 | 366,500,000 | 311,700,000 |
92
+ | SystemRandom | 148,400,000 | 147,600,000 | 64,300,000 | 131,400,000 | 137,300,000 | 58,700,000 | 57,900,000 |
93
+ | UnityRandom | 645,700,000 | 79,900,000 | 83,900,000 | 61,500,000 | 41,300,000 | 76,600,000 | 75,600,000 |
94
+ | WyRandom | 755,900,000 | 356,600,000 | 455,000,000 | 152,300,000 | 190,400,000 | 290,700,000 | 276,100,000 |
95
+ | XorShiftRandom | 784,900,000 | 531,800,000 | 603,300,000 | 178,200,000 | 294,600,000 | 486,500,000 | 392,600,000 |
96
+ | XoroShiroRandom | 783,900,000 | 333,000,000 | 751,900,000 | 145,900,000 | 203,000,000 | 428,000,000 | 381,800,000 |
97
+ | PhotonSpinRandom | 702,200,000 | 217,000,000 | 264,500,000 | 116,000,000 | 116,800,000 | 211,500,000 | 214,100,000 |
98
+ | StormDropRandom | 779,700,000 | 484,800,000 | 717,300,000 | 172,300,000 | 255,800,000 | 401,500,000 | 369,600,000 |
99
99
 
100
100
  <!-- RANDOM_BENCHMARKS_END -->
101
101
 
@@ -33,10 +33,62 @@ When to use it
33
33
 
34
34
  When not to use it
35
35
 
36
- - Oneoff reflection (e.g., editor button pressed infrequently). Simpler `GetValue/SetValue` is fine.
36
+ - One-off reflection (e.g., editor button pressed infrequently). Simpler `GetValue/SetValue` is fine.
37
37
  - If you need full runtime codegen in IL2CPP/WebGL: IL emit isn’t available there. ReflectionHelpers still works, but uses expression compilation or reflection fallback — benefits remain for caching and reduced allocations.
38
38
  - Setting struct instance fields using boxed setters: prefer the generic ref setter to mutate the original struct (see “Struct note” below).
39
39
 
40
+ ### Caching Strategy Overview
41
+
42
+ ReflectionHelpers now partitions cached delegates by **capability strategy** so that expression, dynamic-IL, and reflection fallbacks never overwrite each other. Key points:
43
+
44
+ - **Strategy fingerprinting**: every delegate cache entry is keyed by `CapabilityKey<TMember>` (member metadata + `ReflectionDelegateStrategy`). This applies to fields, properties, indexers, methods, and constructors (boxed + typed variants).
45
+ - **Per-strategy blocklists**: when a strategy cannot produce a delegate (e.g., IL emit disabled on IL2CPP), we record the failure in a per-cache blocklist so later calls skip unnecessary work.
46
+ - **Delegate provenance**: created delegates are tracked in a `ConditionalWeakTable<Delegate, StrategyHolder>` so diagnostics and tests can assert the producing strategy via `ReflectionHelpers.TryGetDelegateStrategy`.
47
+ - **Capability overrides**: `ReflectionHelpers.OverrideReflectionCapabilities(expressions, dynamicIl)` temporarily toggles expression/IL support, letting tests (or runtime feature detection) confirm that caches store independent delegates per strategy.
48
+ - **Test hooks**: `ClearFieldGetterCache`, `ClearPropertyCache`, `ClearMethodCache`, and `ClearConstructorCache` flush the relevant cache groups to keep unit tests deterministic.
49
+ - **Fallback behaviour**: if neither expressions nor dynamic IL are available, the reflection-path delegates still benefit from caching and avoid repeated argument validation/boxing.
50
+
51
+ ### Current Implementation Summary
52
+
53
+ | API Group | Representative methods | Primary strategy (Mono/Editor) | Fallbacks (IL2CPP/WebGL/AOT) | Caching | Notes |
54
+ | -------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
55
+ | Field access (boxed) | `GetFieldGetter(FieldInfo)`, `GetFieldSetter(FieldInfo)` | Emit `DynamicMethod` IL (`BuildFieldGetter/SetterIL`) to cast/unbox target and box return | `CreateCompiled*` builds expression delegates; otherwise wraps `FieldInfo.GetValue/SetValue` | `FieldGetterCache`, `FieldSetterCache`, static equivalents | Supports static + instance fields; struct writes box when IL emit unavailable (IL2CPP/WebGL) |
56
+ | Field access (typed) | `GetFieldGetter<TInstance,TValue>`, `GetFieldSetter<TInstance,TValue>` | Emit typed `DynamicMethod` (setters use by-ref) to avoid boxing | Falls back to `GetValue/SetValue` wrappers; setter fallback boxes then copies back | None (callers must hold returned delegate) | `TInstance` must match declaring type; fastest only where IL emit allowed |
57
+ | Property access (boxed) | `GetPropertyGetter(PropertyInfo)`, `GetPropertySetter(PropertyInfo)` | Emit `DynamicMethod` (`Call`/`Callvirt`) and box value types | Expression-compiled wrapper; else `PropertyInfo.GetValue/SetValue` | `PropertyGetterCache`, `PropertySetterCache`, static equivalents | Handles non-public accessors; fallback reintroduces boxing/allocations |
58
+ | Property access (typed) | `GetPropertyGetter<TInstance,TValue>`, `GetPropertySetter<TInstance,TValue>` | Emit typed `DynamicMethod` with cast/unbox guards | Direct reflection wrappers casting to `TValue` | None | Avoids boxing only on IL paths; static typed getter limited to static properties |
59
+ | Method invokers (boxed) | `GetMethodInvoker`, `GetStaticMethodInvoker`, `InvokeMethod` | Emit `DynamicMethod` to unpack `object[]` args and box return | Expression wrappers; otherwise call `MethodInfo.Invoke` directly | `MethodInvokers`, `StaticMethodInvokers` | Works with private members; fallback incurs reflection cost per call |
60
+ | Method invokers (typed static) | `GetStaticMethodInvoker<…>`, `GetStaticActionInvoker<…>` | Emit `DynamicMethod` per arity (0–4) for direct call | Try `MethodInfo.CreateDelegate`; else expression compile | `TypedStaticInvoker0-4`, `TypedStaticAction0-4` | Signature-checked upfront; limited to four parameters today |
61
+ | Method invokers (typed instance) | `GetInstanceMethodInvoker<TInstance,…>`, `GetInstanceActionInvoker<TInstance,…>` | Emit `DynamicMethod` using `ldarga` for structs and `Callvirt` for refs | Falls back to `Delegate.CreateDelegate` / expression lambdas | `TypedInstanceInvoker0-4`, `TypedInstanceAction0-4` | Requires `TInstance` assignable to declaring type; fallback boxes structs |
62
+ | Constructors & factories | `GetConstructor`, `CreateInstance`, `GetParameterlessConstructor<T>`, `GetParameterlessConstructor` | Delegate factory prefers expression lambdas, falls back to dynamic IL `newobj` and finally reflection (`ConstructorInfo.Invoke` / `Activator.CreateInstance`) | Reflection invoke (no emit) | `Constructors`, `ParameterlessConstructors`, `TypedParameterlessConstructors` | Works across Editor/IL2CPP; capability overrides let tests force fallback paths |
63
+ | Indexer helpers | `GetIndexerGetter`, `GetIndexerSetter` | Expression lambdas or dynamic IL to handle struct receivers and value conversions | Reflection `PropertyInfo.Get/SetValue` with argument validation | `IndexerGetters`, `IndexerSetters` | Throws `IndexOutOfRangeException`/`InvalidCastException` when indices mismatch; respects capability overrides |
64
+ | Collection creators | `CreateArray`, `GetListCreator(Type)`, `GetDictionaryWithCapacityCreator` | Emit `DynamicMethod` for `newarr`/`newobj`, plus `HashSet.Add` wrappers | Use `Array.CreateInstance`, `Activator.CreateInstance`, or reflection `Invoke` | `ArrayCreators`, `ListCreators`, `ListWithCapacityCreators`, `HashSetWithCapacityCreators`, adders | `Create*` APIs cache by element type; fallback still functional but allocates |
65
+ | Type/attribute scanning | `GetAllLoadedAssemblies`, `GetTypesDerivedFrom<T>`, `HasAttributeSafe` | Direct reflection with guarded iteration; Editor uses `UnityEditor.TypeCache` shortcuts | Gracefully skips assemblies/types on error; no IL emit needed | `TypeResolutionCache`, `FieldLookup`, `PropertyLookup`, `MethodLookup` | Depends on link.xml or addressables to keep members under IL2CPP stripping |
66
+
67
+ ### Current Consumers Snapshot
68
+
69
+ - `Runtime/Core/Serialization/Serializer.cs` and `Runtime/Core/Serialization/JsonConverters/TypeConverter.cs` lean on static method invokers and type resolution to integrate ProtoBuf and JSON pipelines.
70
+ - `Runtime/Core/Attributes` (`BaseRelationalComponentAttribute`, `RelationalComponentInitializer`, `NotNullAttribute`) depend on field getters/setters and collection factories for relational wiring.
71
+ - `Runtime/Tags` (`AttributeMetadataCache`, `AttributeUtilities`, `AttributeMetadataFilters`) use attribute scanning plus cached getters/setters to hydrate metadata tables at startup.
72
+ - `Runtime/Core/Helper/StringInList.cs` and `Runtime/Core/Helper/Logging/UnityLogTagFormatter.cs` use helper invokers for dynamic lookups during logging and formatting.
73
+ - `Editor/AnimationEventEditor.cs`, `Editor/Tags/AttributeMetadataCacheGenerator.cs`, and `Editor/Utils/ScriptableObjectSingletonCreator.cs` call into the helpers for TypeCache-driven discovery and editor automation.
74
+ - `Runtime/Utils/ScriptableObjectSingleton.cs` relies on safe attribute retrieval to locate singleton assets without repeating reflection calls.
75
+
76
+ ### Platform Capability Matrix
77
+
78
+ | Target Environment | Unity Backend | `DynamicMethod` IL Emit | `Expression.Compile` | ReflectionHelpers Behaviour | Notes |
79
+ | ---------------------------------------------- | ------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
80
+ | **Editor (Windows/macOS/Linux)** | Mono / JIT | ✅ Enabled (`EMIT_DYNAMIC_IL`) | ✅ Enabled (`SUPPORT_EXPRESSION_COMPILE`) | Uses IL-generated delegates for getters/setters/invokers; expression compile is a fallback if IL creation fails at runtime | Same behaviour for play mode in editor; fastest path used during authoring tools and tests. |
81
+ | **Standalone Player (Mono scripting backend)** | Mono / JIT | ✅ Enabled | ✅ Enabled | Matches editor experience; cached IL delegates provide best throughput | Applies to legacy desktop Mono builds (Windows/Mac/Linux) where JIT is available. |
82
+ | **Standalone / Mobile / Console (IL2CPP)** | IL2CPP / AOT | ❌ Disabled at compile time (`ENABLE_IL2CPP` blocks `EMIT_DYNAMIC_IL`) | ⚠️ Disabled (`SUPPORT_EXPRESSION_COMPILE` undefined; `CheckExpressionCompilationSupport` returns false) | Falls back to pre-built delegate wrappers or direct `Invoke`/`GetValue` with caching; still avoids repeated reflection lookups | Covers Windows/macOS/iOS/Android/Consoles when built with IL2CPP. Requires link.xml (or addressables) to preserve reflected members. |
83
+ | **WebGL Player** | IL2CPP / AOT (wasm) | ❌ Disabled (`UNITY_WEBGL && !UNITY_EDITOR`) | ⚠️ Disabled | Uses expression-free reflection paths identical to IL2CPP builds; object boxing unavoidable for struct setters/invokers | WebGL disallows runtime codegen; helpers rely on cached reflection only. |
84
+ | **Burst-compiled jobs** | Burst | ❌ Not permitted | ❌ Not permitted | ReflectionHelpers should not be called from Burst jobs; wrap calls on main thread or use precomputed data | Burst forbids managed reflection; guard usage with `Unity.Burst.NoAlias` patterns or pre-bake data. |
85
+ | **Server builds / headless (Mono)** | Mono / JIT | ✅ Enabled | ✅ Enabled | Same as desktop Mono path; suitable for dedicated servers running on JIT | Confirm `EMIT_DYNAMIC_IL` stays enabled unless IL2CPP server build is selected. |
86
+ | **Continuous Integration** | Any | Depends on selected backend | Depends on backend | Benchmarks skip doc writes when `Helpers.IsRunningInContinuousIntegration` is true, but helpers themselves behave per backend | Use automated tests to validate both IL2CPP fallback and Mono fast paths. |
87
+
88
+ - `DynamicMethod` support is controlled at compile time by `#if !((UNITY_WEBGL && !UNITY_EDITOR) || ENABLE_IL2CPP)` in `ReflectionHelpers.cs`.
89
+ - `Expression.Compile` support is gated by the same define; the runtime guard `CheckExpressionCompilationSupport()` prevents usage when the platform forbids JIT compilation even if the symbols are present.
90
+ - `SINGLE_THREADED` builds remove `System.Collections.Concurrent` usage and swap to simple dictionaries; this is rarely needed but remains AOT-friendly for constrained platforms.
91
+
40
92
  Key APIs at a glance
41
93
 
42
94
  - Fields
@@ -185,6 +237,37 @@ Performance tips
185
237
  - Prefer typed APIs (`GetFieldGetter<TInstance, TValue>`, typed static invokers) to avoid boxing and object[] allocations.
186
238
  - Use creators (`GetListCreator`, `GetArrayCreator`) in loops to avoid reflection/Activator costs.
187
239
 
240
+ ### Benchmarking & Verification
241
+
242
+ - **Unit coverage**: `ReflectionHelperCapabilityMatrixTests` resets caches and toggles capabilities around each helper. Run these suites in both expression-enabled and expression-disabled modes when changing caching internals.
243
+ - **Micro-benchmarks**: Use `Tests/Runtime/Performance/ReflectionPerformanceTests` to capture before/after numbers for getters, setters, method invokers, and constructors (now including expression vs. dynamic IL comparisons). Record results with each `ReflectionDelegateStrategy` forced via `OverrideReflectionCapabilities` so regressions are easy to spot.
244
+ - **Cache hygiene**: when adding new delegate families, update the appropriate `Clear*Cache` helper and call it from tests to keep scenarios isolated.
245
+ - **Documentation updates**: note the Unity version, scripting backend, and OS whenever you refresh timing data, and sync any tables in the [Reflection Performance docs](Docs/REFLECTION_PERFORMANCE.md) so contributors can compare against baseline numbers.
246
+ - **Execution recipe**:
247
+ 1. Run `Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests` twice—once normally and once with `REFLECTION_HELPERS_FORCE_REFLECTION=1` (or by wrapping the suite in `OverrideReflectionCapabilities(false, false)`) to cover accelerated and fallback paths.
248
+ 2. Export raw benchmark data by running the `ReflectionPerformanceTests` category inside the Unity Test Runner with `LogFullResults` enabled; copy the markdown summary into the [Reflection Performance benchmarks](Docs/REFLECTION_PERFORMANCE.md).
249
+ 3. Validate editor/runtime builds (Mono + IL2CPP) to ensure blocklists behave consistently across backends.
250
+
251
+ ### Testing fallback behaviour
252
+
253
+ When you need to validate the pure-reflection paths (for example, to mimic IL2CPP/WebGL behaviour), override the runtime capability probes inside a `using` scope:
254
+
255
+ ```csharp
256
+ using (ReflectionHelpers.OverrideReflectionCapabilities(expressions: false, dynamicIl: false))
257
+ {
258
+ // Force expression + IL emit to be unavailable
259
+ Func<TestConstructorClass> ctor = ReflectionHelpers.GetParameterlessConstructor<TestConstructorClass>();
260
+ TestConstructorClass instance = ctor(); // Uses reflection fallback
261
+
262
+ PropertyInfo indexer = typeof(IndexerClass).GetProperty("Item");
263
+ var getter = ReflectionHelpers.GetIndexerGetter(indexer);
264
+ var setter = ReflectionHelpers.GetIndexerSetter(indexer);
265
+ setter(new IndexerClass(), 42, new object[] { 0 }); // reflection-based path
266
+ }
267
+ ```
268
+
269
+ The helper restores the original capability state when disposed, so nested overrides remain safe. Runtime regression tests now cover constructors and indexers in both accelerated and fallback modes.
270
+
188
271
  IL2CPP/WebGL notes
189
272
 
190
273
  - Dynamic IL emit is disabled on IL2CPP/WebGL; ReflectionHelpers automatically falls back to expression compilation or direct reflection where necessary.
@@ -0,0 +1,169 @@
1
+ # Reflection Performance Benchmarks
2
+
3
+ Unity Helpers replaces ad-hoc reflection with cached delegates that favour expression lambdas on IL2CPP-safe platforms and fall back to dynamic IL emit or plain reflection where available. These benchmarks compare raw `System.Reflection` against the helpers for common access patterns.
4
+
5
+ Each run updates the table for the current operating system only. Sections that still show `_No benchmark data generated yet._` simply have not been executed on that platform.
6
+
7
+ ## Windows
8
+
9
+ <!-- REFLECTION_PERFORMANCE_WINDOWS_START -->
10
+
11
+ Generated on 2025-11-01 02:10:30 UTC
12
+
13
+ ### Strategy: Default (auto)
14
+
15
+ #### Boxed Access (object)
16
+
17
+ | Scenario | Helper (ops/sec) | System.Reflection (ops/sec) | Speedup vs Reflection |
18
+ | ------------------------------ | ---------------- | --------------------------- | --------------------- |
19
+ | Instance Field Get (boxed) | 26.90M | 6.43M | 4.18x |
20
+ | Instance Field Set (boxed) | 28.65M | 4.50M | 6.36x |
21
+ | Static Field Get (boxed) | 28.95M | 7.23M | 4.00x |
22
+ | Static Field Set (boxed) | 28.12M | 6.28M | 4.48x |
23
+ | Instance Property Get (boxed) | 24.12M | 26.34M | 0.92x |
24
+ | Instance Property Set (boxed) | 27.55M | 1.51M | 18.25x |
25
+ | Static Property Get (boxed) | 26.47M | 27.45M | 0.96x |
26
+ | Static Property Set (boxed) | 22.39M | 2.93M | 7.65x |
27
+ | Instance Method Invoke (boxed) | 23.55M | 1.97M | 11.98x |
28
+ | Static Method Invoke (boxed) | 22.89M | 2.67M | 8.58x |
29
+ | Constructor Invoke (boxed) | 18.01M | 1.70M | 10.58x |
30
+
31
+ #### Typed Access (no boxing)
32
+
33
+ | Scenario | Helper (ops/sec) | Baseline Delegate (ops/sec) | System.Reflection (ops/sec) | Speedup vs Delegate | Speedup vs Reflection |
34
+ | ------------------------------ | ---------------- | --------------------------- | --------------------------- | ------------------- | --------------------- |
35
+ | Instance Field Get (typed) | 544.26M | 614.88M | 6.43M | 0.89x | 84.68x |
36
+ | Instance Field Set (typed) | 636.55M | 669.32M | 4.50M | 0.95x | 141.37x |
37
+ | Static Field Get (typed) | 608.91M | 694.14M | 7.23M | 0.88x | 84.20x |
38
+ | Static Field Set (typed) | 623.04M | 664.87M | 6.28M | 0.94x | 99.27x |
39
+ | Instance Property Get (typed) | 594.54M | 695.13M | 26.34M | 0.86x | 22.57x |
40
+ | Instance Property Set (typed) | 637.69M | 700.01M | 1.51M | 0.91x | 422.38x |
41
+ | Static Property Get (typed) | 626.49M | 692.86M | 27.45M | 0.90x | 22.82x |
42
+ | Static Property Set (typed) | 624.91M | 662.31M | 2.93M | 0.94x | 213.60x |
43
+ | Instance Method Invoke (typed) | 592.71M | 688.29M | 1.97M | 0.86x | 301.58x |
44
+ | Static Method Invoke (typed) | 596.62M | 685.29M | 2.67M | 0.87x | 223.62x |
45
+
46
+ ### Strategy: Expressions
47
+
48
+ #### Boxed Access (object)
49
+
50
+ | Scenario | Helper (ops/sec) | System.Reflection (ops/sec) | Speedup vs Reflection |
51
+ | ------------------------------ | ---------------- | --------------------------- | --------------------- |
52
+ | Instance Field Get (boxed) | 28.61M | 7.44M | 3.84x |
53
+ | Instance Field Set (boxed) | 20.64M | 5.46M | 3.78x |
54
+ | Static Field Get (boxed) | 28.66M | 7.03M | 4.08x |
55
+ | Static Field Set (boxed) | 27.93M | 6.28M | 4.45x |
56
+ | Instance Property Get (boxed) | 21.52M | 25.54M | 0.84x |
57
+ | Instance Property Set (boxed) | 26.48M | 1.71M | 15.50x |
58
+ | Static Property Get (boxed) | 22.02M | 25.15M | 0.88x |
59
+ | Static Property Set (boxed) | 27.33M | 2.14M | 12.79x |
60
+ | Instance Method Invoke (boxed) | 23.92M | 1.95M | 12.28x |
61
+ | Static Method Invoke (boxed) | 27.02M | 2.09M | 12.95x |
62
+ | Constructor Invoke (boxed) | 28.46M | 2.60M | 10.96x |
63
+
64
+ #### Typed Access (no boxing)
65
+
66
+ | Scenario | Helper (ops/sec) | Baseline Delegate (ops/sec) | System.Reflection (ops/sec) | Speedup vs Delegate | Speedup vs Reflection |
67
+ | ------------------------------ | ---------------- | --------------------------- | --------------------------- | ------------------- | --------------------- |
68
+ | Instance Field Get (typed) | 608.49M | 669.38M | 7.44M | 0.91x | 81.76x |
69
+ | Instance Field Set (typed) | 636.85M | 666.03M | 5.46M | 0.96x | 116.70x |
70
+ | Static Field Get (typed) | 600.54M | 692.53M | 7.03M | 0.87x | 85.40x |
71
+ | Static Field Set (typed) | 621.80M | 665.52M | 6.28M | 0.93x | 99.05x |
72
+ | Instance Property Get (typed) | 586.55M | 696.86M | 25.54M | 0.84x | 22.96x |
73
+ | Instance Property Set (typed) | 637.48M | 700.05M | 1.71M | 0.91x | 373.18x |
74
+ | Static Property Get (typed) | 626.64M | 692.26M | 25.15M | 0.91x | 24.92x |
75
+ | Static Property Set (typed) | 617.44M | 652.06M | 2.14M | 0.95x | 288.83x |
76
+ | Instance Method Invoke (typed) | 588.93M | 680.02M | 1.95M | 0.87x | 302.26x |
77
+ | Static Method Invoke (typed) | 604.99M | 677.46M | 2.09M | 0.89x | 289.97x |
78
+
79
+ ### Strategy: Dynamic IL
80
+
81
+ #### Boxed Access (object)
82
+
83
+ | Scenario | Helper (ops/sec) | System.Reflection (ops/sec) | Speedup vs Reflection |
84
+ | ------------------------------ | ---------------- | --------------------------- | --------------------- |
85
+ | Instance Field Get (boxed) | 9.63M | 3.55M | 2.71x |
86
+ | Instance Field Set (boxed) | 28.14M | 5.53M | 5.09x |
87
+ | Static Field Get (boxed) | 21.41M | 8.79M | 2.43x |
88
+ | Static Field Set (boxed) | 28.71M | 5.03M | 5.71x |
89
+ | Instance Property Get (boxed) | 28.48M | 24.35M | 1.17x |
90
+ | Instance Property Set (boxed) | 23.24M | 2.07M | 11.20x |
91
+ | Static Property Get (boxed) | 25.03M | 22.55M | 1.11x |
92
+ | Static Property Set (boxed) | 28.12M | 2.86M | 9.84x |
93
+ | Instance Method Invoke (boxed) | 21.85M | 1.60M | 13.65x |
94
+ | Static Method Invoke (boxed) | 26.94M | 2.66M | 10.14x |
95
+ | Constructor Invoke (boxed) | 26.74M | 2.27M | 11.79x |
96
+
97
+ #### Typed Access (no boxing)
98
+
99
+ | Scenario | Helper (ops/sec) | Baseline Delegate (ops/sec) | System.Reflection (ops/sec) | Speedup vs Delegate | Speedup vs Reflection |
100
+ | ------------------------------ | ---------------- | --------------------------- | --------------------------- | ------------------- | --------------------- |
101
+ | Instance Field Get (typed) | 596.97M | 665.13M | 3.55M | 0.90x | 168.16x |
102
+ | Instance Field Set (typed) | 628.92M | 658.63M | 5.53M | 0.95x | 113.81x |
103
+ | Static Field Get (typed) | 610.06M | 693.76M | 8.79M | 0.88x | 69.40x |
104
+ | Static Field Set (typed) | 629.61M | 658.60M | 5.03M | 0.96x | 125.18x |
105
+ | Instance Property Get (typed) | 595.85M | 687.51M | 24.35M | 0.87x | 24.47x |
106
+ | Instance Property Set (typed) | 631.56M | 692.86M | 2.07M | 0.91x | 304.37x |
107
+ | Static Property Get (typed) | 620.22M | 684.44M | 22.55M | 0.91x | 27.51x |
108
+ | Static Property Set (typed) | 611.44M | 655.34M | 2.86M | 0.93x | 213.92x |
109
+ | Instance Method Invoke (typed) | 588.65M | 680.04M | 1.60M | 0.87x | 367.87x |
110
+ | Static Method Invoke (typed) | 600.46M | 677.57M | 2.66M | 0.89x | 226.04x |
111
+
112
+ ### Strategy: Reflection Fallback
113
+
114
+ #### Boxed Access (object)
115
+
116
+ | Scenario | Helper (ops/sec) | System.Reflection (ops/sec) | Speedup vs Reflection |
117
+ | ------------------------------ | ---------------- | --------------------------- | --------------------- |
118
+ | Instance Field Get (boxed) | 7.29M | 7.32M | 0.99x |
119
+ | Instance Field Set (boxed) | 4.40M | 5.47M | 0.80x |
120
+ | Static Field Get (boxed) | 8.55M | 4.84M | 1.77x |
121
+ | Static Field Set (boxed) | 2.72M | 4.55M | 0.60x |
122
+ | Instance Property Get (boxed) | 26.15M | 26.84M | 0.97x |
123
+ | Instance Property Set (boxed) | 1.81M | 2.10M | 0.86x |
124
+ | Static Property Get (boxed) | 22.27M | 26.45M | 0.84x |
125
+ | Static Property Set (boxed) | 2.55M | 2.96M | 0.86x |
126
+ | Instance Method Invoke (boxed) | 2.00M | 1.97M | 1.02x |
127
+ | Static Method Invoke (boxed) | 2.47M | 1.91M | 1.30x |
128
+ | Constructor Invoke (boxed) | 2.55M | 2.57M | 0.99x |
129
+
130
+ #### Typed Access (no boxing)
131
+
132
+ | Scenario | Helper (ops/sec) | Baseline Delegate (ops/sec) | System.Reflection (ops/sec) | Speedup vs Delegate | Speedup vs Reflection |
133
+ | ------------------------------ | ---------------- | --------------------------- | --------------------------- | ------------------- | --------------------- |
134
+ | Instance Field Get (typed) | 7.28M | 625.84M | 7.32M | 0.01x | 0.99x |
135
+ | Instance Field Set (typed) | 5.29M | 654.42M | 5.47M | 0.01x | 0.97x |
136
+ | Static Field Get (typed) | 7.07M | 691.26M | 4.84M | 0.01x | 1.46x |
137
+ | Static Field Set (typed) | 6.19M | 658.10M | 4.55M | 0.01x | 1.36x |
138
+ | Instance Property Get (typed) | 590.82M | 688.57M | 26.84M | 0.86x | 22.01x |
139
+ | Instance Property Set (typed) | 631.83M | 696.20M | 2.10M | 0.91x | 300.24x |
140
+ | Static Property Get (typed) | 626.19M | 690.57M | 26.45M | 0.91x | 23.67x |
141
+ | Static Property Set (typed) | 621.38M | 662.49M | 2.96M | 0.94x | 210.10x |
142
+ | Instance Method Invoke (typed) | 587.96M | 689.37M | 1.97M | 0.85x | 298.85x |
143
+ | Static Method Invoke (typed) | 604.95M | 685.12M | 1.91M | 0.88x | 317.35x |
144
+
145
+ <!-- REFLECTION_PERFORMANCE_WINDOWS_END -->
146
+
147
+ ## macOS
148
+
149
+ <!-- REFLECTION_PERFORMANCE_MACOS_START -->
150
+
151
+ _No benchmark data generated yet._
152
+
153
+ <!-- REFLECTION_PERFORMANCE_MACOS_END -->
154
+
155
+ ## Linux
156
+
157
+ <!-- REFLECTION_PERFORMANCE_LINUX_START -->
158
+
159
+ _No benchmark data generated yet._
160
+
161
+ <!-- REFLECTION_PERFORMANCE_LINUX_END -->
162
+
163
+ ## Unknown / Other
164
+
165
+ <!-- REFLECTION_PERFORMANCE_UNKNOWN_START -->
166
+
167
+ _No benchmark data generated yet._
168
+
169
+ <!-- REFLECTION_PERFORMANCE_UNKNOWN_END -->
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: eb975aaa9a7b55842a63ac0105034aa4
2
+ guid: 1a27457c1ce047a68daffaad6fd63704
3
3
  TextScriptImporter:
4
4
  externalObjects: {}
5
5
  userData:
@@ -23,6 +23,8 @@ Having issues? Jump to Troubleshooting: see [Troubleshooting](#troubleshooting).
23
23
 
24
24
  Related systems: For data‑driven gameplay effects (attributes, tags, cosmetics), see [Effects System](EFFECTS_SYSTEM.md) and the README section Effects, Attributes, and Tags (#effects-attributes-and-tags).
25
25
 
26
+ Curious how these attributes stack up against manual `GetComponent*` loops? Check the [Relational Component Performance Benchmarks](RELATIONAL_COMPONENT_PERFORMANCE.md) for operations-per-second and allocation snapshots.
27
+
26
28
  ## TL;DR — What Problem This Solves
27
29
 
28
30
  - **⭐ Replace 20+ lines of repetitive GetComponent boilerplate with 3 attributes + 1 method call.**
@@ -208,6 +210,8 @@ Examples:
208
210
  [SiblingComponent] private HashSet<Renderer> allRenderers; // HashSet<T> supported
209
211
  ```
210
212
 
213
+ > **Performance note:** Sibling lookups do not cache results between calls. In profiling we found these assignments typically run once per GameObject (e.g., during `Awake`), so the extra bookkeeping and invalidation cost of a cache outweighed the benefits. If you need updated references later, call `AssignSiblingComponents` again after the hierarchy changes.
214
+
211
215
  ### ParentComponent
212
216
 
213
217
  - Scope: Up the transform chain (optionally excluding self)
@@ -247,6 +251,8 @@ Examples:
247
251
  [ChildComponent(OnlyDescendants = true, MaxCount = 10)] private HashSet<Rigidbody2D> firstTenRigidbodies;
248
252
  ```
249
253
 
254
+ > **Performance note:** When you avoid depth limits and interface filtering, child assignments run through a cached `GetComponentsInChildren<T>()` delegate to stay allocation-free. Turning on `MaxDepth` or interface searches still works, but the assigner reverts to the breadth-first traversal to honour those constraints.
255
+
250
256
  ## Common Options (All Attributes)
251
257
 
252
258
  - `Optional` (default: false)
@@ -0,0 +1,63 @@
1
+ # Relational Component Performance Benchmarks
2
+
3
+ Relational component attributes (`[SiblingComponent]`, `[ParentComponent]`, `[ChildComponent]`) remove repetitive `GetComponent*` code. These benchmarks quantify the runtime cost of calling `Assign*Components` for common field shapes (single component, array, `List<T>`, and `HashSet<T>`) against hand-written lookups.
4
+
5
+ **How to refresh these tables:**
6
+
7
+ 1. Open Unity’s Test Runner (EditMode/PlayMode as appropriate for your setup).
8
+ 2. Run `RelationalComponentBenchmarkTests.Benchmark` inside `Tests/Runtime/Performance`.
9
+ 3. The test logs the tables to the console and rewrites the section that matches the current operating system.
10
+
11
+ The script executes the benchmark test in batch mode, captures the markdown tables to `BenchmarkLogs/RelationalBenchmark.log`, and preserves the raw `TestResults.xml` when `-KeepResults` is specified.
12
+
13
+ ## Windows (Editor/Player)
14
+
15
+ <!-- RELATIONAL_COMPONENTS_WINDOWS_START -->
16
+
17
+ _Last updated 2025-11-01 02:11 UTC on Windows 11 (10.0.26200) 64bit_
18
+
19
+ Numbers capture repeated `Assign*Components` calls for one second per scenario.
20
+ Higher operations per second are better.
21
+
22
+ ### Operations per second (higher is better)
23
+
24
+ | Scenario | Relational Ops/s | Manual Ops/s | Rel/Manual | Iterations |
25
+ | ----------------- | ---------------: | -----------: | ---------: | ---------: |
26
+ | Parent - Single | 1,009,079 | 5,700,123 | 0.18x | 1,010,000 |
27
+ | Parent - Array | 676,149 | 3,327,317 | 0.20x | 680,000 |
28
+ | Parent - List | 766,704 | 4,238,343 | 0.18x | 770,000 |
29
+ | Parent - HashSet | 746,757 | 2,906,359 | 0.26x | 750,000 |
30
+ | Child - Single | 948,288 | 3,562,341 | 0.27x | 950,000 |
31
+ | Child - Array | 263,898 | 2,357,185 | 0.11x | 270,000 |
32
+ | Child - List | 247,098 | 2,623,979 | 0.09x | 250,000 |
33
+ | Child - HashSet | 247,462 | 1,708,595 | 0.14x | 250,000 |
34
+ | Sibling - Single | 3,855,983 | 14,542,964 | 0.27x | 3,860,000 |
35
+ | Sibling - Array | 925,080 | 2,537,309 | 0.36x | 930,000 |
36
+ | Sibling - List | 1,170,239 | 3,356,368 | 0.35x | 1,180,000 |
37
+ | Sibling - HashSet | 1,137,739 | 1,773,824 | 0.64x | 1,140,000 |
38
+
39
+ <!-- RELATIONAL_COMPONENTS_WINDOWS_END -->
40
+
41
+ ## macOS
42
+
43
+ <!-- RELATIONAL_COMPONENTS_MACOS_START -->
44
+
45
+ Pending — run the relational component benchmark suite on macOS to capture results.
46
+
47
+ <!-- RELATIONAL_COMPONENTS_MACOS_END -->
48
+
49
+ ## Linux
50
+
51
+ <!-- RELATIONAL_COMPONENTS_LINUX_START -->
52
+
53
+ Pending — run the relational component benchmark suite on Linux to capture results.
54
+
55
+ <!-- RELATIONAL_COMPONENTS_LINUX_END -->
56
+
57
+ ## Other Platforms
58
+
59
+ <!-- RELATIONAL_COMPONENTS_OTHER_START -->
60
+
61
+ Pending — run the relational component benchmark suite on the target platform to capture results.
62
+
63
+ <!-- RELATIONAL_COMPONENTS_OTHER_END -->
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 13aa38c2854c08049b8fc1851c504f08
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant: