com.wallstop-studios.unity-helpers 2.0.0-rc81.9 → 2.0.0

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 (168) hide show
  1. package/.editorconfig +1 -1
  2. package/.gitattributes +1 -1
  3. package/.githooks/pre-commit +31 -5
  4. package/.githooks/pre-push +50 -0
  5. package/.github/dependabot.yml +24 -2
  6. package/.github/scripts/check-markdown-links.ps1 +77 -0
  7. package/.github/scripts/check_markdown_links.py +89 -0
  8. package/.github/scripts/check_markdown_url_encoding.py +74 -0
  9. package/.github/scripts/validate_markdown_links.py +194 -0
  10. package/.github/workflows/csharpier-autofix.yml +152 -0
  11. package/.github/workflows/format-on-demand.yml +305 -0
  12. package/.github/workflows/lint-doc-links.yml +8 -5
  13. package/.github/workflows/markdown-json.yml +6 -2
  14. package/.github/workflows/prettier-autofix.yml +195 -0
  15. package/.github/workflows/update-dotnet-tools.yml +80 -0
  16. package/.github/workflows/yaml-format-lint.yml +41 -0
  17. package/.lychee.toml +4 -4
  18. package/.markdownlint.jsonc +21 -0
  19. package/.pre-commit-config.yaml +11 -3
  20. package/.yamllint.yaml +31 -0
  21. package/AGENTS.md +5 -1
  22. package/CHANGELOG.md +11 -0
  23. package/CONTRIBUTING.md +49 -0
  24. package/CONTRIBUTING.md.meta +7 -0
  25. package/EDITOR_TOOLS_GUIDE.md +4 -0
  26. package/Editor/AnimationEventEditor.cs +337 -160
  27. package/Editor/Core/Helper/AnimationEventHelpers.cs +178 -152
  28. package/Editor/CustomEditors/PersistentDirectoryGUI.cs +20 -11
  29. package/Editor/CustomEditors/TexturePlatformOverrideEntryDrawer.cs +11 -2
  30. package/Editor/FitTextureSizeWindow.cs +43 -19
  31. package/Editor/PersistentDirectorySettings.cs +64 -12
  32. package/Editor/PrefabChecker.cs +72 -5
  33. package/Editor/Sprites/AnimationCopier.cs +132 -56
  34. package/Editor/Sprites/AnimationCreator.cs +63 -22
  35. package/Editor/Sprites/AnimationViewerWindow.cs +42 -6
  36. package/Editor/Sprites/TexturePlatformNameHelper.cs +50 -39
  37. package/Editor/Sprites/TextureResizerWizard.cs +23 -1
  38. package/Editor/Sprites/TextureSettingsApplierWindow.cs +148 -85
  39. package/Editor/Tools/ImageBlurTool.cs +81 -10
  40. package/Editor/Utils/EditorUi.cs +1 -1
  41. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +1 -1
  42. package/GETTING_STARTED.md +40 -56
  43. package/RANDOM_PERFORMANCE.md +12 -12
  44. package/README.md +395 -2407
  45. package/RELATIONAL_COMPONENTS.md +92 -83
  46. package/Runtime/AssemblyInfo.cs +2 -0
  47. package/Runtime/Core/Attributes/NotNullAttribute.cs +1 -3
  48. package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +50 -5
  49. package/Runtime/Core/DataStructure/CyclicBuffer.cs +0 -1
  50. package/Runtime/Core/Extension/RandomExtensions.cs +68 -0
  51. package/Runtime/Core/Extension/WallstopStudiosLogger.cs +16 -0
  52. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +4 -1
  53. package/Runtime/Core/Helper/ReflectionHelpers.cs +21 -10
  54. package/Runtime/Core/Helper/SpriteHelpers.cs +3 -1
  55. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +45 -1
  56. package/Runtime/Core/Serialization/JsonConverters/GameObjectConverter.cs +13 -5
  57. package/Runtime/Core/Serialization/JsonConverters/ResolutionConverter.cs +1 -1
  58. package/Runtime/Core/Serialization/JsonConverters/TypeConverter.cs +1 -1
  59. package/Runtime/Core/Serialization/Serializer.cs +101 -0
  60. package/Runtime/Integrations/VContainer/AssemblyInfo.cs +9 -0
  61. package/Runtime/Integrations/VContainer/AssemblyInfo.cs.meta +3 -0
  62. package/Runtime/Integrations/VContainer/ObjectResolverRelationalExtensions.cs +96 -0
  63. package/Runtime/Integrations/VContainer/RelationalComponentEntryPoint.cs +90 -10
  64. package/Runtime/Integrations/VContainer/RelationalComponentsBuilderExtensions.cs +13 -1
  65. package/Runtime/Integrations/VContainer/RelationalObjectPools.cs +114 -0
  66. package/Runtime/Integrations/VContainer/RelationalObjectPools.cs.meta +11 -0
  67. package/Runtime/Integrations/VContainer/RelationalSceneAssignmentOptions.cs +16 -4
  68. package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs +241 -0
  69. package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs.meta +11 -0
  70. package/Runtime/Integrations/Zenject/AssemblyInfo.cs +9 -0
  71. package/Runtime/Integrations/Zenject/AssemblyInfo.cs.meta +3 -0
  72. package/Runtime/Integrations/Zenject/DiContainerRelationalExtensions.cs +69 -2
  73. package/Runtime/Integrations/Zenject/RelationalComponentSceneInitializer.cs +89 -12
  74. package/Runtime/Integrations/Zenject/RelationalComponentsInstaller.cs +23 -1
  75. package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs +44 -0
  76. package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs.meta +11 -0
  77. package/Runtime/Integrations/Zenject/RelationalSceneAssignmentOptions.cs +16 -10
  78. package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs +243 -0
  79. package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs.meta +11 -0
  80. package/Runtime/Tags/AttributeMetadataCache.cs +1 -4
  81. package/Runtime/Utils/Buffers.cs +4 -4
  82. package/Runtime/Utils/ScriptableObjectSingleton.cs +0 -1
  83. package/Runtime/Utils/SetTextureImportData.cs +3 -1
  84. package/Runtime/Utils/TextureScale.cs +10 -2
  85. package/Runtime/Visuals/UGUI/EnhancedImage.cs +6 -0
  86. package/Runtime/Visuals/UIToolkit/LayeredImage.cs +4 -1
  87. package/SERIALIZATION.md +15 -0
  88. package/SPATIAL_TREE_2D_PERFORMANCE.md +85 -82
  89. package/SPATIAL_TREE_3D_PERFORMANCE.md +94 -91
  90. package/Samples~/DI - VContainer/README.md +232 -51
  91. package/Samples~/DI - VContainer/Scripts/GameLifetimeScope.cs +22 -4
  92. package/Samples~/DI - VContainer/Scripts/RelationalConsumer.cs +5 -2
  93. package/Samples~/DI - VContainer/Scripts/Spawner.cs +113 -4
  94. package/Samples~/DI - Zenject/README.md +217 -53
  95. package/Samples~/DI - Zenject/Scripts/RelationalConsumer.cs +3 -0
  96. package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs +37 -0
  97. package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs.meta +12 -0
  98. package/Samples~/DI - Zenject/Scripts/SpawnerZenject.cs +74 -3
  99. package/Samples~/Random - PRNG/README.md +2 -1
  100. package/Samples~/Relational Components - Basic/README.md +3 -1
  101. package/Samples~/Serialization - JSON/README.md +2 -1
  102. package/Samples~/Spatial Structures - 2D and 3D/README.md +2 -1
  103. package/Samples~/UGUI - EnhancedImage/README.md +2 -1
  104. package/Samples~/UI Toolkit - MultiFile Selector (Editor)/README.md +2 -1
  105. package/THIRD_PARTY_NOTICES.md +1 -1
  106. package/Tests/Editor/Attributes/AnimationEventHelpersTests.cs +16 -0
  107. package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +3 -3
  108. package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +6 -2
  109. package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +170 -0
  110. package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs.meta +11 -0
  111. package/Tests/Editor/Integrations/VContainer/WallstopStudios.UnityHelpers.Tests.Editor.VContainer.asmdef +2 -1
  112. package/Tests/Editor/Integrations/Zenject/WallstopStudios.UnityHelpers.Tests.Editor.Zenject.asmdef +3 -2
  113. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +131 -0
  114. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs.meta +11 -0
  115. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +6 -2
  116. package/Tests/Editor/PersistentDirectorySettingsTests.cs +59 -0
  117. package/Tests/Editor/PersistentDirectorySettingsTests.cs.meta +11 -0
  118. package/Tests/Editor/PrefabCheckerReportTests.cs +32 -0
  119. package/Tests/Editor/PrefabCheckerReportTests.cs.meta +11 -0
  120. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +64 -0
  121. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs.meta +11 -0
  122. package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +1 -1
  123. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +38 -0
  124. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs.meta +11 -0
  125. package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +1 -1
  126. package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +12 -12
  127. package/Tests/Editor/Sprites/SpriteCropperTests.cs +9 -9
  128. package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +3 -3
  129. package/Tests/Editor/Sprites/TexturePlatformNameHelperTests.cs +18 -0
  130. package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +5 -5
  131. package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +3 -3
  132. package/Tests/Editor/Sprites/TextureSettingsApplierWizardAdditionalTests.cs +4 -4
  133. package/Tests/Editor/Sprites/TextureSettingsApplierWizardTests.cs +4 -4
  134. package/Tests/Editor/Tools/ImageBlurToolTests.cs +22 -110
  135. package/Tests/Editor/Utils/CommonTestBase.cs +43 -1
  136. package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +5 -5
  137. package/Tests/Editor/Windows/FitTextureSizeWindowTests.cs +66 -74
  138. package/Tests/Runtime/Attributes/RelationalComponentInitializerTests.cs +4 -15
  139. package/Tests/Runtime/DataStructures/SpatialTree3DBoundsConsistencyTests.cs +29 -29
  140. package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +259 -218
  141. package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +86 -0
  142. package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs.meta +11 -0
  143. package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +255 -227
  144. package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +5 -0
  145. package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +3 -0
  146. package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs +30 -0
  147. package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs.meta +11 -0
  148. package/Tests/Runtime/Serialization/JsonConverterTests.cs +8 -12
  149. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +16 -5
  150. package/Tests/Runtime/Serialization/SerializerAdditionalTests.cs +12 -0
  151. package/Tests/Runtime/Serialization/SerializerFileIoTests.cs +105 -0
  152. package/Tests/Runtime/Serialization/SerializerFileIoTests.cs.meta +11 -0
  153. package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs +247 -0
  154. package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs.meta +11 -0
  155. package/Tests/Runtime/TestUtils/CommonTestBase.cs +88 -0
  156. package/Tests/Runtime/Utils/CoroutineHandlerTests.cs +1 -1
  157. package/Tests/Runtime/Utils/LZMAComprehensiveTests.cs +1 -1
  158. package/Tests/Runtime/Utils/LZMATests.cs +1 -1
  159. package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +1 -1
  160. package/Tests/Runtime/Visuals/EnhancedImageTests.cs +25 -56
  161. package/Tests/Runtime/Visuals/VisualsTestHelpers.cs +1 -8
  162. package/package-lock.json.meta +7 -0
  163. package/package.json +8 -4
  164. package/scripts/check-eol.ps1 +4 -5
  165. package/scripts/lint-tests.ps1 +156 -0
  166. package/scripts/lint-tests.ps1.meta +7 -0
  167. package/scripts/normalize-eol.ps1 +6 -9
  168. package/.github/workflows/csharpier.yml +0 -135
@@ -277,6 +277,10 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
277
277
  None = 0,
278
278
 
279
279
  /// <summary>Legacy .NET BinaryFormatter. Trusted/ephemeral data only.</summary>
280
+ [Obsolete(
281
+ "BinaryFormatter is obsolete and unsafe for untrusted data. "
282
+ + "Prefer Json or Protobuf for new code."
283
+ )]
280
284
  SystemBinary = 1,
281
285
 
282
286
  /// <summary>protobuf-net compact binary. Best for networking and high-performance.</summary>
@@ -608,7 +612,9 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
608
612
  {
609
613
  switch (serializationType)
610
614
  {
615
+ #pragma warning disable CS0618 // Type or member is obsolete
611
616
  case SerializationType.SystemBinary:
617
+ #pragma warning restore CS0618 // Type or member is obsolete
612
618
  {
613
619
  return BinaryDeserialize<T>(serialized);
614
620
  }
@@ -650,7 +656,9 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
650
656
  {
651
657
  switch (serializationType)
652
658
  {
659
+ #pragma warning disable CS0618 // Type or member is obsolete
653
660
  case SerializationType.SystemBinary:
661
+ #pragma warning restore CS0618 // Type or member is obsolete
654
662
  {
655
663
  return BinarySerialize(instance);
656
664
  }
@@ -689,7 +697,9 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
689
697
  {
690
698
  switch (serializationType)
691
699
  {
700
+ #pragma warning disable CS0618 // Type or member is obsolete
692
701
  case SerializationType.SystemBinary:
702
+ #pragma warning restore CS0618 // Type or member is obsolete
693
703
  {
694
704
  return BinarySerialize(instance, ref buffer);
695
705
  }
@@ -1494,6 +1504,35 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
1494
1504
  return JsonDeserialize<T>(settingsAsBytes);
1495
1505
  }
1496
1506
 
1507
+ /// <summary>
1508
+ /// Asynchronously reads JSON with cancellation.
1509
+ /// </summary>
1510
+ public static async Task<T> ReadFromJsonFileAsync<T>(
1511
+ string path,
1512
+ System.Threading.CancellationToken cancellationToken
1513
+ )
1514
+ {
1515
+ using FileStream fs = new(
1516
+ path,
1517
+ FileMode.Open,
1518
+ FileAccess.Read,
1519
+ FileShare.Read,
1520
+ 4096,
1521
+ useAsync: true
1522
+ );
1523
+ using Utils.PooledResource<PooledBufferStream> lease = PooledBufferStream.Rent(
1524
+ out PooledBufferStream stream
1525
+ );
1526
+ byte[] buffer = new byte[8192];
1527
+ int read;
1528
+ while ((read = await fs.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
1529
+ {
1530
+ stream.Write(buffer, 0, read);
1531
+ }
1532
+ ArraySegment<byte> seg = stream.GetWrittenSegment();
1533
+ return JsonDeserialize<T>(seg.Array.AsSpan(0, seg.Count).ToArray());
1534
+ }
1535
+
1497
1536
  /// <summary>
1498
1537
  /// Writes an instance to a JSON file (UTF‑8).
1499
1538
  /// </summary>
@@ -1520,6 +1559,29 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
1520
1559
  await File.WriteAllTextAsync(path, jsonAsText);
1521
1560
  }
1522
1561
 
1562
+ /// <summary>
1563
+ /// Asynchronously writes an instance to a JSON file (UTF‑8) with cancellation.
1564
+ /// </summary>
1565
+ public static async Task WriteToJsonFileAsync<T>(
1566
+ T input,
1567
+ string path,
1568
+ bool pretty,
1569
+ System.Threading.CancellationToken cancellationToken
1570
+ )
1571
+ {
1572
+ string jsonAsText = JsonStringify(input, pretty);
1573
+ byte[] bytes = SerializerEncoding.Encoding.GetBytes(jsonAsText);
1574
+ using FileStream fs = new(
1575
+ path,
1576
+ FileMode.Create,
1577
+ FileAccess.Write,
1578
+ FileShare.None,
1579
+ 4096,
1580
+ useAsync: true
1581
+ );
1582
+ await fs.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
1583
+ }
1584
+
1523
1585
  /// <summary>
1524
1586
  /// Writes an instance to a JSON file (UTF‑8) using the provided <paramref name="options"/>.
1525
1587
  /// </summary>
@@ -1549,6 +1611,45 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization
1549
1611
  string jsonAsText = JsonStringify(input, options);
1550
1612
  await File.WriteAllTextAsync(path, jsonAsText);
1551
1613
  }
1614
+
1615
+ /// <summary>
1616
+ /// Attempts to read JSON into an instance, returns false if file missing or invalid.
1617
+ /// </summary>
1618
+ public static bool TryReadFromJsonFile<T>(string path, out T value)
1619
+ {
1620
+ try
1621
+ {
1622
+ if (!File.Exists(path))
1623
+ {
1624
+ value = default;
1625
+ return false;
1626
+ }
1627
+ string json = File.ReadAllText(path);
1628
+ value = JsonDeserialize<T>(json);
1629
+ return true;
1630
+ }
1631
+ catch
1632
+ {
1633
+ value = default;
1634
+ return false;
1635
+ }
1636
+ }
1637
+
1638
+ /// <summary>
1639
+ /// Attempts to write JSON to a file, returns false on failure.
1640
+ /// </summary>
1641
+ public static bool TryWriteToJsonFile<T>(T input, string path, bool pretty = true)
1642
+ {
1643
+ try
1644
+ {
1645
+ WriteToJsonFile(input, path, pretty);
1646
+ return true;
1647
+ }
1648
+ catch
1649
+ {
1650
+ return false;
1651
+ }
1652
+ }
1552
1653
  }
1553
1654
 
1554
1655
  // Internal pooled, growable write stream backed by ArrayPool<byte> to reduce allocations
@@ -0,0 +1,9 @@
1
+ using System.Runtime.CompilerServices;
2
+
3
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Editor")]
4
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Runtime")]
5
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Editor")]
6
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Runtime.Zenject")]
7
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Runtime.VContainer")]
8
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Editor.VContainer")]
9
+ [assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Editor.Zenject")]
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 87d075d4bb0645c2a11721a306a1de00
3
+ timeCreated: 1760388352
@@ -1,8 +1,10 @@
1
1
  #if VCONTAINER_PRESENT
2
2
  namespace WallstopStudios.UnityHelpers.Integrations.VContainer
3
3
  {
4
+ using System;
4
5
  using System.Collections.Generic;
5
6
  using global::VContainer;
7
+ using global::VContainer.Unity;
6
8
  using UnityEngine;
7
9
  using WallstopStudios.UnityHelpers.Core.Attributes;
8
10
  using WallstopStudios.UnityHelpers.Utils;
@@ -56,6 +58,27 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
56
58
  /// </example>
57
59
  public static class ObjectResolverRelationalExtensions
58
60
  {
61
+ /// <summary>
62
+ /// Injects <paramref name="component"/> with VContainer and assigns its relational fields.
63
+ /// </summary>
64
+ /// <typeparam name="T">The component type.</typeparam>
65
+ /// <param name="resolver">The VContainer object resolver.</param>
66
+ /// <param name="component">The component instance to inject and hydrate.</param>
67
+ /// <returns>The same component instance.</returns>
68
+ public static T InjectWithRelations<T>(this IObjectResolver resolver, T component)
69
+ where T : Component
70
+ {
71
+ if (component == null)
72
+ {
73
+ return null;
74
+ }
75
+
76
+ // Use Inject for compatibility with VContainer 1.16.x
77
+ resolver?.Inject(component);
78
+ resolver.AssignRelationalComponents(component);
79
+ return component;
80
+ }
81
+
59
82
  /// <summary>
60
83
  /// Assigns all relational fields on a component using the container's registered
61
84
  /// <see cref="IRelationalComponentAssigner"/> if present, with a safe fallback to the
@@ -167,6 +190,79 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
167
190
  return component;
168
191
  }
169
192
 
193
+ /// <summary>
194
+ /// Instantiates a prefab that has component <typeparamref name="T"/> on the root, injects it
195
+ /// using VContainer and assigns relational fields.
196
+ /// </summary>
197
+ /// <typeparam name="T">Component type on the prefab root.</typeparam>
198
+ /// <param name="resolver">The VContainer object resolver.</param>
199
+ /// <param name="prefab">Prefab that contains <typeparamref name="T"/>.</param>
200
+ /// <param name="parent">Optional parent transform for the new instance.</param>
201
+ /// <returns>The instantiated component with DI and relational fields populated.</returns>
202
+ public static T InstantiateComponentWithRelations<T>(
203
+ this IObjectResolver resolver,
204
+ T prefab,
205
+ Transform parent = null
206
+ )
207
+ where T : Component
208
+ {
209
+ if (prefab == null)
210
+ {
211
+ throw new ArgumentNullException(nameof(prefab));
212
+ }
213
+
214
+ T instance = UnityEngine.Object.Instantiate(prefab, parent);
215
+ return resolver.BuildUpWithRelations(instance);
216
+ }
217
+
218
+ /// <summary>
219
+ /// Instantiates a GameObject prefab, injects its hierarchy with VContainer, then assigns
220
+ /// relational fields for all components beneath the root.
221
+ /// </summary>
222
+ /// <param name="resolver">The VContainer object resolver.</param>
223
+ /// <param name="prefab">GameObject prefab to instantiate.</param>
224
+ /// <param name="parent">Optional parent transform.</param>
225
+ /// <param name="includeInactiveChildren">Whether to include inactive children when assigning.</param>
226
+ /// <returns>The instantiated GameObject.</returns>
227
+ public static GameObject InstantiateGameObjectWithRelations(
228
+ this IObjectResolver resolver,
229
+ GameObject prefab,
230
+ Transform parent = null,
231
+ bool includeInactiveChildren = true
232
+ )
233
+ {
234
+ if (prefab == null)
235
+ {
236
+ throw new ArgumentNullException(nameof(prefab));
237
+ }
238
+
239
+ GameObject instance = UnityEngine.Object.Instantiate(prefab, parent);
240
+ resolver.InjectGameObjectWithRelations(instance, includeInactiveChildren);
241
+ return instance;
242
+ }
243
+
244
+ /// <summary>
245
+ /// Injects all components on <paramref name="root"/> and its children, then assigns
246
+ /// relational fields for the hierarchy.
247
+ /// </summary>
248
+ /// <param name="resolver">The VContainer object resolver.</param>
249
+ /// <param name="root">Root GameObject to inject and hydrate.</param>
250
+ /// <param name="includeInactiveChildren">Whether to include inactive children when assigning.</param>
251
+ public static void InjectGameObjectWithRelations(
252
+ this IObjectResolver resolver,
253
+ GameObject root,
254
+ bool includeInactiveChildren = true
255
+ )
256
+ {
257
+ if (root == null)
258
+ {
259
+ return;
260
+ }
261
+
262
+ resolver?.InjectGameObject(root);
263
+ resolver.AssignRelationalHierarchy(root, includeInactiveChildren);
264
+ }
265
+
170
266
  private static bool TryResolveAssigner(
171
267
  IObjectResolver resolver,
172
268
  out IRelationalComponentAssigner assigner
@@ -9,6 +9,7 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
9
9
  using WallstopStudios.UnityHelpers.Core.Attributes;
10
10
  using WallstopStudios.UnityHelpers.Tags;
11
11
  using WallstopStudios.UnityHelpers.Utils;
12
+ using Object = UnityEngine.Object;
12
13
 
13
14
  /// <summary>
14
15
  /// Entry point registered with VContainer to hydrate relational attributes once the container is
@@ -71,38 +72,117 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
71
72
 
72
73
  if (relationalTypes.Count == 0)
73
74
  {
75
+ // Fallback: scan all components once and assign when type has relational fields
76
+ bool includeInactiveAll = _options.IncludeInactive;
77
+ Component[] allComponents = includeInactiveAll
78
+ ? Object.FindObjectsOfType<Component>(true)
79
+ : Object.FindObjectsOfType<Component>(false);
80
+
81
+ for (int i = 0; i < allComponents.Length; i++)
82
+ {
83
+ Component c = allComponents[i];
84
+ if (c == null || c.gameObject.scene != SceneManager.GetActiveScene())
85
+ {
86
+ continue;
87
+ }
88
+
89
+ if (_assigner.HasRelationalAssignments(c.GetType()))
90
+ {
91
+ _assigner.Assign(c);
92
+ }
93
+ }
74
94
  return;
75
95
  }
76
96
 
77
97
  bool includeInactive = _options.IncludeInactive;
78
98
  Scene activeScene = SceneManager.GetActiveScene();
79
99
 
80
- foreach (Type componentType in relationalTypes)
100
+ if (_options.UseSinglePassScan)
81
101
  {
82
- if (componentType == null)
102
+ using PooledResource<HashSet<Type>> pooledSet = Buffers<Type>.HashSet.Get(
103
+ out HashSet<Type> relationalSet
104
+ );
105
+ for (int i = 0; i < relationalTypes.Count; i++)
83
106
  {
84
- continue;
107
+ Type t = relationalTypes[i];
108
+ if (t != null)
109
+ {
110
+ relationalSet.Add(t);
111
+ }
85
112
  }
86
113
 
87
- UnityEngine.Object[] located = includeInactive
88
- ? UnityEngine.Object.FindObjectsOfType(componentType, true)
89
- : UnityEngine.Object.FindObjectsOfType(componentType, false);
114
+ Component[] all = includeInactive
115
+ ? Object.FindObjectsOfType<Component>(true)
116
+ : Object.FindObjectsOfType<Component>(false);
90
117
 
91
- foreach (UnityEngine.Object candidate in located)
118
+ for (int i = 0; i < all.Length; i++)
92
119
  {
93
- if (candidate is not Component component)
120
+ Component c = all[i];
121
+ if (c == null || c.gameObject.scene != activeScene)
94
122
  {
95
123
  continue;
96
124
  }
97
125
 
98
- if (component.gameObject.scene != activeScene)
126
+ Type t = c.GetType();
127
+ while (t != null && typeof(Component).IsAssignableFrom(t))
128
+ {
129
+ if (relationalSet.Contains(t))
130
+ {
131
+ _assigner.Assign(c);
132
+ break;
133
+ }
134
+ t = t.BaseType;
135
+ }
136
+ }
137
+ }
138
+ else
139
+ {
140
+ foreach (Type componentType in relationalTypes)
141
+ {
142
+ if (componentType == null)
99
143
  {
100
144
  continue;
101
145
  }
102
146
 
103
- _assigner.Assign(component);
147
+ Object[] located = includeInactive
148
+ ? Object.FindObjectsOfType(componentType, true)
149
+ : Object.FindObjectsOfType(componentType, false);
150
+
151
+ foreach (Object t in located)
152
+ {
153
+ if (t is not Component component)
154
+ {
155
+ continue;
156
+ }
157
+
158
+ if (component.gameObject.scene != activeScene)
159
+ {
160
+ continue;
161
+ }
162
+
163
+ _assigner.Assign(component);
164
+ }
104
165
  }
105
166
  }
167
+
168
+ // Safety net in Editor/tests: also walk scene roots to catch any missed
169
+ #if UNITY_EDITOR
170
+ if (!Application.isPlaying)
171
+ {
172
+ using PooledResource<List<GameObject>> rootGoBuffer = Buffers<GameObject>.List.Get(
173
+ out List<GameObject> roots
174
+ );
175
+ activeScene.GetRootGameObjects(roots);
176
+ foreach (GameObject root in roots)
177
+ {
178
+ if (root == null)
179
+ {
180
+ continue;
181
+ }
182
+ _assigner.AssignHierarchy(root, includeInactive);
183
+ }
184
+ }
185
+ #endif
106
186
  }
107
187
  }
108
188
  }
@@ -14,6 +14,8 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
14
14
  /// <remarks>
15
15
  /// Registers the shared <see cref="IRelationalComponentAssigner"/> as a singleton and schedules a
16
16
  /// scene-wide entry point that hydrates all relational fields after the container has been built.
17
+ /// Optionally wires <see cref="RelationalSceneLoadListener"/> so future additive scenes receive
18
+ /// the same treatment.
17
19
  /// </remarks>
18
20
  public static class RelationalComponentsBuilderExtensions
19
21
  {
@@ -26,6 +28,10 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
26
28
  /// Optional settings to control how the active scene is scanned (e.g., include inactive
27
29
  /// objects). When <c>null</c>, <see cref="RelationalSceneAssignmentOptions.Default"/> is used.
28
30
  /// </param>
31
+ /// <param name="enableAdditiveSceneListener">
32
+ /// When true registers <see cref="RelationalSceneLoadListener"/> so additively loaded scenes
33
+ /// are hydrated with the same options. Disable when you manage additive scenes manually.
34
+ /// </param>
29
35
  /// <example>
30
36
  /// <code>
31
37
  /// using VContainer;
@@ -49,7 +55,8 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
49
55
  /// </example>
50
56
  public static void RegisterRelationalComponents(
51
57
  this IContainerBuilder builder,
52
- RelationalSceneAssignmentOptions? options = null
58
+ RelationalSceneAssignmentOptions? options = null,
59
+ bool enableAdditiveSceneListener = true
53
60
  )
54
61
  {
55
62
  if (builder == null)
@@ -72,6 +79,11 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
72
79
  .AsSelf();
73
80
 
74
81
  builder.RegisterEntryPoint<RelationalComponentEntryPoint>().WithParameter(resolved);
82
+
83
+ if (enableAdditiveSceneListener)
84
+ {
85
+ builder.RegisterEntryPoint<RelationalSceneLoadListener>().WithParameter(resolved);
86
+ }
75
87
  }
76
88
  }
77
89
  }
@@ -0,0 +1,114 @@
1
+ #if VCONTAINER_PRESENT
2
+ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
3
+ {
4
+ using System;
5
+ using global::VContainer;
6
+ using global::VContainer.Unity;
7
+ using UnityEngine;
8
+ using UnityEngine.Pool;
9
+
10
+ /// <summary>
11
+ /// Helpers for creating UnityEngine.Pool object pools plus extensions that hydrate pooled items
12
+ /// through VContainer when you rent them via <see cref="GetWithRelations{T}(ObjectPool{T},IObjectResolver)"/>.
13
+ /// </summary>
14
+ public static class RelationalObjectPools
15
+ {
16
+ /// <summary>
17
+ /// Creates a component pool to be combined with <see cref="GetWithRelations{T}(ObjectPool{T},IObjectResolver)"/>
18
+ /// so items are injected and hydrated on rental time.
19
+ /// </summary>
20
+ public static ObjectPool<T> CreatePoolWithRelations<T>(
21
+ Func<T> createFunc,
22
+ Action<T> actionOnGet = null,
23
+ Action<T> actionOnRelease = null,
24
+ Action<T> actionOnDestroy = null,
25
+ bool collectionCheck = true,
26
+ int defaultCapacity = 10,
27
+ int maxSize = 10000
28
+ )
29
+ where T : Component
30
+ {
31
+ if (createFunc == null)
32
+ {
33
+ throw new ArgumentNullException(nameof(createFunc));
34
+ }
35
+
36
+ return new ObjectPool<T>(
37
+ createFunc,
38
+ actionOnGet: actionOnGet,
39
+ actionOnRelease: actionOnRelease,
40
+ actionOnDestroy: actionOnDestroy,
41
+ collectionCheck: collectionCheck,
42
+ defaultCapacity: defaultCapacity,
43
+ maxSize: maxSize
44
+ );
45
+ }
46
+
47
+ /// <summary>
48
+ /// Creates a GameObject pool to be combined with
49
+ /// <see cref="GetWithRelations(ObjectPool{GameObject},IObjectResolver)"/> so hierarchies are
50
+ /// injected and hydrated on rental time.
51
+ /// </summary>
52
+ public static ObjectPool<GameObject> CreateGameObjectPoolWithRelations(
53
+ GameObject prefab,
54
+ Transform parent = null,
55
+ Action<GameObject> actionOnGet = null,
56
+ Action<GameObject> actionOnRelease = null,
57
+ Action<GameObject> actionOnDestroy = null,
58
+ bool collectionCheck = true,
59
+ int defaultCapacity = 10,
60
+ int maxSize = 10000
61
+ )
62
+ {
63
+ if (prefab == null)
64
+ {
65
+ throw new ArgumentNullException(nameof(prefab));
66
+ }
67
+
68
+ Func<GameObject> create = () => UnityEngine.Object.Instantiate(prefab, parent);
69
+
70
+ return new ObjectPool<GameObject>(
71
+ create,
72
+ actionOnGet: actionOnGet,
73
+ actionOnRelease: actionOnRelease,
74
+ actionOnDestroy: actionOnDestroy,
75
+ collectionCheck: collectionCheck,
76
+ defaultCapacity: defaultCapacity,
77
+ maxSize: maxSize
78
+ );
79
+ }
80
+
81
+ /// <summary>
82
+ /// Rents an item from the pool, injects and assigns relational fields.
83
+ /// </summary>
84
+ public static T GetWithRelations<T>(this ObjectPool<T> pool, IObjectResolver resolver)
85
+ where T : Component
86
+ {
87
+ T item = pool.Get();
88
+ if (item != null)
89
+ {
90
+ resolver?.Inject(item);
91
+ resolver.AssignRelationalComponents(item);
92
+ }
93
+ return item;
94
+ }
95
+
96
+ /// <summary>
97
+ /// Rents a GameObject from the pool, injects the hierarchy and assigns relational fields.
98
+ /// </summary>
99
+ public static GameObject GetWithRelations(
100
+ this ObjectPool<GameObject> pool,
101
+ IObjectResolver resolver
102
+ )
103
+ {
104
+ GameObject go = pool.Get();
105
+ if (go != null)
106
+ {
107
+ resolver?.InjectGameObject(go);
108
+ resolver.AssignRelationalHierarchy(go, includeInactiveChildren: true);
109
+ }
110
+ return go;
111
+ }
112
+ }
113
+ }
114
+ #endif
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: fa576218812adfe4b98444f975a1cbf4
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -2,6 +2,7 @@
2
2
  namespace WallstopStudios.UnityHelpers.Integrations.VContainer
3
3
  {
4
4
  using System;
5
+ using WallstopStudios.UnityHelpers.Core.Helper;
5
6
 
6
7
  /// <summary>
7
8
  /// Controls how the VContainer integration applies relational component assignment.
@@ -24,25 +25,36 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
24
25
  /// When true the entry point will scan inactive scene objects so that relational fields are
25
26
  /// populated even for disabled hierarchies. Defaults to <c>true</c>.
26
27
  /// </param>
27
- public RelationalSceneAssignmentOptions(bool includeInactive)
28
+ /// <param name="useSinglePassScan">
29
+ /// When true uses a single-pass scene scan for performance (recommended). Defaults to
30
+ /// <c>true</c>.
31
+ /// </param>
32
+ public RelationalSceneAssignmentOptions(bool includeInactive, bool useSinglePassScan = true)
28
33
  {
29
34
  IncludeInactive = includeInactive;
35
+ UseSinglePassScan = useSinglePassScan;
30
36
  }
31
37
 
32
38
  /// <summary>
33
39
  /// Options used when no explicit configuration is supplied.
34
40
  /// </summary>
35
- public static RelationalSceneAssignmentOptions Default => new(includeInactive: true);
41
+ public static RelationalSceneAssignmentOptions Default => new(true, true);
36
42
 
37
43
  /// <summary>
38
44
  /// Gets whether inactive GameObjects should be included when scanning the scene.
39
45
  /// </summary>
40
46
  public bool IncludeInactive { get; }
41
47
 
48
+ /// <summary>
49
+ /// Gets whether to use a single-pass scene scan to locate relational components.
50
+ /// </summary>
51
+ public bool UseSinglePassScan { get; }
52
+
42
53
  /// <inheritdoc />
43
54
  public bool Equals(RelationalSceneAssignmentOptions other)
44
55
  {
45
- return IncludeInactive == other.IncludeInactive;
56
+ return IncludeInactive == other.IncludeInactive
57
+ && UseSinglePassScan == other.UseSinglePassScan;
46
58
  }
47
59
 
48
60
  /// <inheritdoc />
@@ -54,7 +66,7 @@ namespace WallstopStudios.UnityHelpers.Integrations.VContainer
54
66
  /// <inheritdoc />
55
67
  public override int GetHashCode()
56
68
  {
57
- return IncludeInactive.GetHashCode();
69
+ return Objects.HashCode(IncludeInactive, UseSinglePassScan);
58
70
  }
59
71
 
60
72
  /// <summary>