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.
- package/.editorconfig +1 -1
- package/.gitattributes +1 -1
- package/.githooks/pre-commit +31 -5
- package/.githooks/pre-push +50 -0
- package/.github/dependabot.yml +24 -2
- package/.github/scripts/check-markdown-links.ps1 +77 -0
- package/.github/scripts/check_markdown_links.py +89 -0
- package/.github/scripts/check_markdown_url_encoding.py +74 -0
- package/.github/scripts/validate_markdown_links.py +194 -0
- package/.github/workflows/csharpier-autofix.yml +152 -0
- package/.github/workflows/format-on-demand.yml +305 -0
- package/.github/workflows/lint-doc-links.yml +8 -5
- package/.github/workflows/markdown-json.yml +6 -2
- package/.github/workflows/prettier-autofix.yml +195 -0
- package/.github/workflows/update-dotnet-tools.yml +80 -0
- package/.github/workflows/yaml-format-lint.yml +41 -0
- package/.lychee.toml +4 -4
- package/.markdownlint.jsonc +21 -0
- package/.pre-commit-config.yaml +11 -3
- package/.yamllint.yaml +31 -0
- package/AGENTS.md +5 -1
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +49 -0
- package/CONTRIBUTING.md.meta +7 -0
- package/EDITOR_TOOLS_GUIDE.md +4 -0
- package/Editor/AnimationEventEditor.cs +337 -160
- package/Editor/Core/Helper/AnimationEventHelpers.cs +178 -152
- package/Editor/CustomEditors/PersistentDirectoryGUI.cs +20 -11
- package/Editor/CustomEditors/TexturePlatformOverrideEntryDrawer.cs +11 -2
- package/Editor/FitTextureSizeWindow.cs +43 -19
- package/Editor/PersistentDirectorySettings.cs +64 -12
- package/Editor/PrefabChecker.cs +72 -5
- package/Editor/Sprites/AnimationCopier.cs +132 -56
- package/Editor/Sprites/AnimationCreator.cs +63 -22
- package/Editor/Sprites/AnimationViewerWindow.cs +42 -6
- package/Editor/Sprites/TexturePlatformNameHelper.cs +50 -39
- package/Editor/Sprites/TextureResizerWizard.cs +23 -1
- package/Editor/Sprites/TextureSettingsApplierWindow.cs +148 -85
- package/Editor/Tools/ImageBlurTool.cs +81 -10
- package/Editor/Utils/EditorUi.cs +1 -1
- package/Editor/Utils/ScriptableObjectSingletonCreator.cs +1 -1
- package/GETTING_STARTED.md +40 -56
- package/RANDOM_PERFORMANCE.md +12 -12
- package/README.md +395 -2407
- package/RELATIONAL_COMPONENTS.md +92 -83
- package/Runtime/AssemblyInfo.cs +2 -0
- package/Runtime/Core/Attributes/NotNullAttribute.cs +1 -3
- package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +50 -5
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +0 -1
- package/Runtime/Core/Extension/RandomExtensions.cs +68 -0
- package/Runtime/Core/Extension/WallstopStudiosLogger.cs +16 -0
- package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +4 -1
- package/Runtime/Core/Helper/ReflectionHelpers.cs +21 -10
- package/Runtime/Core/Helper/SpriteHelpers.cs +3 -1
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +45 -1
- package/Runtime/Core/Serialization/JsonConverters/GameObjectConverter.cs +13 -5
- package/Runtime/Core/Serialization/JsonConverters/ResolutionConverter.cs +1 -1
- package/Runtime/Core/Serialization/JsonConverters/TypeConverter.cs +1 -1
- package/Runtime/Core/Serialization/Serializer.cs +101 -0
- package/Runtime/Integrations/VContainer/AssemblyInfo.cs +9 -0
- package/Runtime/Integrations/VContainer/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Integrations/VContainer/ObjectResolverRelationalExtensions.cs +96 -0
- package/Runtime/Integrations/VContainer/RelationalComponentEntryPoint.cs +90 -10
- package/Runtime/Integrations/VContainer/RelationalComponentsBuilderExtensions.cs +13 -1
- package/Runtime/Integrations/VContainer/RelationalObjectPools.cs +114 -0
- package/Runtime/Integrations/VContainer/RelationalObjectPools.cs.meta +11 -0
- package/Runtime/Integrations/VContainer/RelationalSceneAssignmentOptions.cs +16 -4
- package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs +241 -0
- package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs.meta +11 -0
- package/Runtime/Integrations/Zenject/AssemblyInfo.cs +9 -0
- package/Runtime/Integrations/Zenject/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Integrations/Zenject/DiContainerRelationalExtensions.cs +69 -2
- package/Runtime/Integrations/Zenject/RelationalComponentSceneInitializer.cs +89 -12
- package/Runtime/Integrations/Zenject/RelationalComponentsInstaller.cs +23 -1
- package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs +44 -0
- package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs.meta +11 -0
- package/Runtime/Integrations/Zenject/RelationalSceneAssignmentOptions.cs +16 -10
- package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs +243 -0
- package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs.meta +11 -0
- package/Runtime/Tags/AttributeMetadataCache.cs +1 -4
- package/Runtime/Utils/Buffers.cs +4 -4
- package/Runtime/Utils/ScriptableObjectSingleton.cs +0 -1
- package/Runtime/Utils/SetTextureImportData.cs +3 -1
- package/Runtime/Utils/TextureScale.cs +10 -2
- package/Runtime/Visuals/UGUI/EnhancedImage.cs +6 -0
- package/Runtime/Visuals/UIToolkit/LayeredImage.cs +4 -1
- package/SERIALIZATION.md +15 -0
- package/SPATIAL_TREE_2D_PERFORMANCE.md +85 -82
- package/SPATIAL_TREE_3D_PERFORMANCE.md +94 -91
- package/Samples~/DI - VContainer/README.md +232 -51
- package/Samples~/DI - VContainer/Scripts/GameLifetimeScope.cs +22 -4
- package/Samples~/DI - VContainer/Scripts/RelationalConsumer.cs +5 -2
- package/Samples~/DI - VContainer/Scripts/Spawner.cs +113 -4
- package/Samples~/DI - Zenject/README.md +217 -53
- package/Samples~/DI - Zenject/Scripts/RelationalConsumer.cs +3 -0
- package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs +37 -0
- package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs.meta +12 -0
- package/Samples~/DI - Zenject/Scripts/SpawnerZenject.cs +74 -3
- package/Samples~/Random - PRNG/README.md +2 -1
- package/Samples~/Relational Components - Basic/README.md +3 -1
- package/Samples~/Serialization - JSON/README.md +2 -1
- package/Samples~/Spatial Structures - 2D and 3D/README.md +2 -1
- package/Samples~/UGUI - EnhancedImage/README.md +2 -1
- package/Samples~/UI Toolkit - MultiFile Selector (Editor)/README.md +2 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/Tests/Editor/Attributes/AnimationEventHelpersTests.cs +16 -0
- package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +3 -3
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +6 -2
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +170 -0
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs.meta +11 -0
- package/Tests/Editor/Integrations/VContainer/WallstopStudios.UnityHelpers.Tests.Editor.VContainer.asmdef +2 -1
- package/Tests/Editor/Integrations/Zenject/WallstopStudios.UnityHelpers.Tests.Editor.Zenject.asmdef +3 -2
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +131 -0
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs.meta +11 -0
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +6 -2
- package/Tests/Editor/PersistentDirectorySettingsTests.cs +59 -0
- package/Tests/Editor/PersistentDirectorySettingsTests.cs.meta +11 -0
- package/Tests/Editor/PrefabCheckerReportTests.cs +32 -0
- package/Tests/Editor/PrefabCheckerReportTests.cs.meta +11 -0
- package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +64 -0
- package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs.meta +11 -0
- package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +1 -1
- package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +38 -0
- package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs.meta +11 -0
- package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +1 -1
- package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +12 -12
- package/Tests/Editor/Sprites/SpriteCropperTests.cs +9 -9
- package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +3 -3
- package/Tests/Editor/Sprites/TexturePlatformNameHelperTests.cs +18 -0
- package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +5 -5
- package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +3 -3
- package/Tests/Editor/Sprites/TextureSettingsApplierWizardAdditionalTests.cs +4 -4
- package/Tests/Editor/Sprites/TextureSettingsApplierWizardTests.cs +4 -4
- package/Tests/Editor/Tools/ImageBlurToolTests.cs +22 -110
- package/Tests/Editor/Utils/CommonTestBase.cs +43 -1
- package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +5 -5
- package/Tests/Editor/Windows/FitTextureSizeWindowTests.cs +66 -74
- package/Tests/Runtime/Attributes/RelationalComponentInitializerTests.cs +4 -15
- package/Tests/Runtime/DataStructures/SpatialTree3DBoundsConsistencyTests.cs +29 -29
- package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +259 -218
- package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +86 -0
- package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs.meta +11 -0
- package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +255 -227
- package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +5 -0
- package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +3 -0
- package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs +30 -0
- package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs.meta +11 -0
- package/Tests/Runtime/Serialization/JsonConverterTests.cs +8 -12
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +16 -5
- package/Tests/Runtime/Serialization/SerializerAdditionalTests.cs +12 -0
- package/Tests/Runtime/Serialization/SerializerFileIoTests.cs +105 -0
- package/Tests/Runtime/Serialization/SerializerFileIoTests.cs.meta +11 -0
- package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs +247 -0
- package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs.meta +11 -0
- package/Tests/Runtime/TestUtils/CommonTestBase.cs +88 -0
- package/Tests/Runtime/Utils/CoroutineHandlerTests.cs +1 -1
- package/Tests/Runtime/Utils/LZMAComprehensiveTests.cs +1 -1
- package/Tests/Runtime/Utils/LZMATests.cs +1 -1
- package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +1 -1
- package/Tests/Runtime/Visuals/EnhancedImageTests.cs +25 -56
- package/Tests/Runtime/Visuals/VisualsTestHelpers.cs +1 -8
- package/package-lock.json.meta +7 -0
- package/package.json +8 -4
- package/scripts/check-eol.ps1 +4 -5
- package/scripts/lint-tests.ps1 +156 -0
- package/scripts/lint-tests.ps1.meta +7 -0
- package/scripts/normalize-eol.ps1 +6 -9
- package/.github/workflows/csharpier.yml +0 -135
package/RELATIONAL_COMPONENTS.md
CHANGED
|
@@ -314,114 +314,102 @@ Notes:
|
|
|
314
314
|
|
|
315
315
|
## Dependency Injection Integrations
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
**Stop choosing between DI and clean hierarchy references** - Unity Helpers provides seamless integrations with Zenject and VContainer that automatically wire up your relational component fields right after dependency injection completes.
|
|
318
318
|
|
|
319
|
-
|
|
319
|
+
### The DI Pain Point
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
- Consistent behavior: integrates with constructor/property injection and works with runtime instantiation.
|
|
323
|
-
- Safe fallback: if the DI binding is missing, falls back to the non-DI path so fields still populate.
|
|
321
|
+
Without these integrations, you're stuck writing `Awake()` methods full of `GetComponent` boilerplate **even when using a DI framework**:
|
|
324
322
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
323
|
+
```csharp
|
|
324
|
+
public class Enemy : MonoBehaviour
|
|
325
|
+
{
|
|
326
|
+
[Inject] private IHealthSystem _health; // ✅ DI handles this
|
|
329
327
|
|
|
330
|
-
|
|
328
|
+
private Animator _animator; // ❌ Still manual boilerplate
|
|
329
|
+
private Rigidbody2D _rigidbody; // ❌ Still manual boilerplate
|
|
331
330
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
331
|
+
void Awake()
|
|
332
|
+
{
|
|
333
|
+
_animator = GetComponent<Animator>();
|
|
334
|
+
_rigidbody = GetComponent<Rigidbody2D>();
|
|
335
|
+
// ... 15 more lines of GetComponent hell
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
338
339
|
|
|
339
|
-
###
|
|
340
|
+
### The Integration Solution
|
|
340
341
|
|
|
341
|
-
|
|
342
|
+
With the DI integrations, **everything just works**:
|
|
342
343
|
|
|
343
344
|
```csharp
|
|
344
|
-
|
|
345
|
-
using VContainer.Unity;
|
|
346
|
-
using WallstopStudios.UnityHelpers.Integrations.VContainer;
|
|
347
|
-
|
|
348
|
-
public sealed class GameLifetimeScope : LifetimeScope
|
|
345
|
+
public class Enemy : MonoBehaviour
|
|
349
346
|
{
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
builder.RegisterRelationalComponents();
|
|
347
|
+
[Inject] private IHealthSystem _health; // ✅ DI injection
|
|
348
|
+
[SiblingComponent] private Animator _animator; // ✅ Relational auto-wiring
|
|
349
|
+
[SiblingComponent] private Rigidbody2D _rigidbody; // ✅ Relational auto-wiring
|
|
354
350
|
|
|
355
|
-
|
|
356
|
-
// builder.RegisterRelationalComponents(new RelationalSceneAssignmentOptions(includeInactive: false));
|
|
357
|
-
}
|
|
351
|
+
// No Awake() needed! Both DI and hierarchy references wired automatically
|
|
358
352
|
}
|
|
359
353
|
```
|
|
360
354
|
|
|
361
|
-
|
|
355
|
+
### Why Use the DI Integrations
|
|
362
356
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
357
|
+
- **Zero boilerplate** - No `Awake()` method needed, no manual `GetComponent` calls, no validation code
|
|
358
|
+
- **Consistent behavior** - Works seamlessly with constructor/property/field injection and runtime instantiation
|
|
359
|
+
- **Safe fallback** - Gracefully degrades to standard behavior if DI binding is missing
|
|
360
|
+
- **Risk-free adoption** - Use incrementally, mix DI and non-DI components freely
|
|
367
361
|
|
|
368
|
-
|
|
369
|
-
{
|
|
370
|
-
[Inject] private IObjectResolver _resolver;
|
|
371
|
-
[SerializeField] private Enemy _enemyPrefab;
|
|
362
|
+
### Supported Packages (Auto-detected)
|
|
372
363
|
|
|
373
|
-
|
|
374
|
-
{
|
|
375
|
-
Enemy enemy = Instantiate(_enemyPrefab, parent);
|
|
376
|
-
return _resolver.BuildUpWithRelations(enemy);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
```
|
|
364
|
+
Unity Helpers automatically detects these packages via UPM:
|
|
380
365
|
|
|
381
|
-
|
|
366
|
+
- **Zenject/Extenject**: `com.extenject.zenject`, `com.modesttree.zenject`, `com.svermeulen.extenject`
|
|
367
|
+
- **VContainer**: `jp.cysharp.vcontainer`, `jp.hadashikick.vcontainer`
|
|
382
368
|
|
|
383
|
-
|
|
384
|
-
_resolver.AssignRelationalHierarchy(root, includeInactiveChildren: false);
|
|
385
|
-
```
|
|
369
|
+
> 💡 **UPM packages work out-of-the-box** - No scripting defines needed!
|
|
386
370
|
|
|
387
|
-
###
|
|
371
|
+
### Manual or Source Imports (Non-UPM)
|
|
388
372
|
|
|
389
|
-
|
|
373
|
+
If you import Zenject/VContainer as source code, .unitypackage, or raw DLLs (not via UPM), you need to manually add scripting defines:
|
|
390
374
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
-
|
|
375
|
+
1. Open `Project Settings > Player > Other Settings > Scripting Define Symbols`
|
|
376
|
+
2. Add the appropriate define(s) for your target platforms:
|
|
377
|
+
- `ZENJECT_PRESENT` - When using Zenject/Extenject
|
|
378
|
+
- `VCONTAINER_PRESENT` - When using VContainer
|
|
379
|
+
3. Unity will recompile and the integration assemblies under `Runtime/Integrations/*` will activate automatically
|
|
394
380
|
|
|
395
|
-
|
|
381
|
+
### VContainer at a Glance
|
|
396
382
|
|
|
397
|
-
|
|
398
|
-
using UnityEngine;
|
|
399
|
-
using Zenject;
|
|
400
|
-
using WallstopStudios.UnityHelpers.Integrations.Zenject;
|
|
383
|
+
- **Enable once per scope**
|
|
401
384
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
385
|
+
```csharp
|
|
386
|
+
builder.RegisterRelationalComponents(
|
|
387
|
+
new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true),
|
|
388
|
+
enableAdditiveSceneListener: true
|
|
389
|
+
);
|
|
390
|
+
```
|
|
406
391
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
392
|
+
- **Runtime helpers**
|
|
393
|
+
- `_resolver.InstantiateComponentWithRelations(componentPrefab, parent)`
|
|
394
|
+
- `_resolver.InstantiateGameObjectWithRelations(rootPrefab, parent, includeInactiveChildren: true)`
|
|
395
|
+
- `_resolver.AssignRelationalHierarchy(existingRoot, includeInactiveChildren: true)`
|
|
396
|
+
- `RelationalObjectPools.CreatePoolWithRelations(...)` + `pool.GetWithRelations(resolver)`
|
|
412
397
|
|
|
413
|
-
|
|
414
|
-
{
|
|
415
|
-
return _container.InstantiateComponentWithRelations(_enemyPrefab, parent);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
```
|
|
398
|
+
- **Full walkthrough**: [DI – VContainer sample](Samples~/DI%20-%20VContainer/README.md)
|
|
419
399
|
|
|
420
|
-
|
|
400
|
+
### Zenject at a Glance
|
|
421
401
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
402
|
+
- **Install once per scene**
|
|
403
|
+
- Add `RelationalComponentsInstaller` to your `SceneContext`.
|
|
404
|
+
- Toggles cover include-inactive scanning, single-pass strategy, and additive-scene listening.
|
|
405
|
+
|
|
406
|
+
- **Runtime helpers**
|
|
407
|
+
- `_container.InstantiateComponentWithRelations(componentPrefab, parent)`
|
|
408
|
+
- `_container.InstantiateGameObjectWithRelations(rootPrefab, parent, includeInactiveChildren: true)`
|
|
409
|
+
- `_container.AssignRelationalHierarchy(existingRoot, includeInactiveChildren: true)`
|
|
410
|
+
- Subclass `RelationalMemoryPool<T>` to hydrate pooled items on spawn.
|
|
411
|
+
|
|
412
|
+
- **Full walkthrough**: [DI – Zenject sample](Samples~/DI%20-%20Zenject/README.md)
|
|
425
413
|
|
|
426
414
|
Notes
|
|
427
415
|
|
|
@@ -479,7 +467,21 @@ Beginner-friendly overview
|
|
|
479
467
|
|
|
480
468
|
VContainer (1.16.x)
|
|
481
469
|
|
|
482
|
-
- Runtime usage (LifetimeScope): Call `builder.RegisterRelationalComponents()` in `LifetimeScope.Configure`. The entry point runs automatically after the container builds.
|
|
470
|
+
- Runtime usage (LifetimeScope): Call `builder.RegisterRelationalComponents()` in `LifetimeScope.Configure`. The entry point runs automatically after the container builds. You can enable an additive-scene listener and customize scan options:
|
|
471
|
+
|
|
472
|
+
```csharp
|
|
473
|
+
using VContainer;
|
|
474
|
+
using VContainer.Unity;
|
|
475
|
+
using WallstopStudios.UnityHelpers.Integrations.VContainer;
|
|
476
|
+
|
|
477
|
+
protected override void Configure(IContainerBuilder builder)
|
|
478
|
+
{
|
|
479
|
+
// Single-pass scan + additive scene listener
|
|
480
|
+
var options = new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true);
|
|
481
|
+
builder.RegisterRelationalComponents(options, enableAdditiveSceneListener: true);
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
483
485
|
- Tests without LifetimeScope: Construct the entry point and call `Initialize()` yourself, and register your `AttributeMetadataCache` instance so the assigner uses it:
|
|
484
486
|
|
|
485
487
|
```csharp
|
|
@@ -500,7 +502,8 @@ VContainer (1.16.x)
|
|
|
500
502
|
entry.Initialize();
|
|
501
503
|
```
|
|
502
504
|
|
|
503
|
-
- Inject vs BuildUp: Use `resolver.
|
|
505
|
+
- Inject vs BuildUp: Use `resolver.InjectWithRelations(component)` to inject + assign in one call, or `resolver.Inject(component)` then `resolver.AssignRelationalComponents(component)`.
|
|
506
|
+
- Prefabs & GameObjects: `resolver.InstantiateComponentWithRelations(prefab, parent)` or `resolver.InstantiateGameObjectWithRelations(prefab, parent)`; to inject existing hierarchies use `resolver.InjectGameObjectWithRelations(root)`.
|
|
504
507
|
|
|
505
508
|
- EditMode reliability: In EditMode tests, prefer `[UnityTest]` and `yield return null` after creating objects and after initializing the entry point so Unity has a frame to register new objects before `FindObjectsOfType` runs and to allow assignments to complete.
|
|
506
509
|
- Active scene filter: Entry points operate on the active scene only. In EditMode, create a new scene with `SceneManager.CreateScene`, set it active, and move your test hierarchy into it before calling `Initialize()`.
|
|
@@ -508,10 +511,16 @@ VContainer (1.16.x)
|
|
|
508
511
|
|
|
509
512
|
Zenject/Extenject
|
|
510
513
|
|
|
511
|
-
- Runtime usage: Add `RelationalComponentsInstaller` to your `SceneContext`. It binds `IRelationalComponentAssigner` and runs `RelationalComponentSceneInitializer` once the container is ready.
|
|
514
|
+
- Runtime usage: Add `RelationalComponentsInstaller` to your `SceneContext`. It binds `IRelationalComponentAssigner` and runs `RelationalComponentSceneInitializer` once the container is ready. The installer exposes toggles to assign on initialize and to listen for additive scenes.
|
|
512
515
|
- Tests: Bind a concrete `AttributeMetadataCache` instance and construct the assigner with that cache. Then resolve `IInitializable` and call `Initialize()`.
|
|
513
516
|
- EditMode reliability: As with VContainer, consider `[UnityTest]` with a `yield return null` after creating objects and after calling `Initialize()` to allow Unity to register objects and complete assignments.
|
|
514
|
-
- Active scene filter:
|
|
517
|
+
- Active scene filter: Initial one-time scan operates on the active scene only. The additive-scene listener processes only newly loaded scenes (not all loaded scenes).
|
|
518
|
+
- Prefabs & GameObjects: `container.InstantiateComponentWithRelations(...)`, `container.InstantiateGameObjectWithRelations(...)`, or `container.InjectGameObjectWithRelations(root)`; to inject + assign a single instance: `container.InjectWithRelations(component)`.
|
|
519
|
+
|
|
520
|
+
### Object Pools (DI-aware)
|
|
521
|
+
|
|
522
|
+
- Zenject: use `RelationalMemoryPool<T>` (or `<TParam, T>`) to assign relational fields in `OnSpawned` automatically.
|
|
523
|
+
- VContainer: create pools with `RelationalObjectPools.CreatePoolWithRelations(...)` and rent via `pool.GetWithRelations(resolver)` to inject + assign.
|
|
515
524
|
|
|
516
525
|
Common pitfalls and how to avoid them
|
|
517
526
|
|
package/Runtime/AssemblyInfo.cs
CHANGED
|
@@ -5,3 +5,5 @@ using System.Runtime.CompilerServices;
|
|
|
5
5
|
[assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Editor")]
|
|
6
6
|
[assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Runtime.Zenject")]
|
|
7
7
|
[assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Runtime.VContainer")]
|
|
8
|
+
[assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Editor.VContainer")]
|
|
9
|
+
[assembly: InternalsVisibleTo("WallstopStudios.UnityHelpers.Tests.Editor.Zenject")]
|
|
@@ -13,9 +13,7 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
|
|
|
13
13
|
{
|
|
14
14
|
#if UNITY_EDITOR
|
|
15
15
|
IEnumerable<FieldInfo> properties =
|
|
16
|
-
|
|
17
|
-
o.GetType()
|
|
18
|
-
);
|
|
16
|
+
Helper.ReflectionHelpers.GetFieldsWithAttribute<NotNullAttribute>(o.GetType());
|
|
19
17
|
|
|
20
18
|
foreach (FieldInfo field in properties)
|
|
21
19
|
{
|
|
@@ -2,6 +2,7 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
|
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
4
|
using System.Collections.Generic;
|
|
5
|
+
using System.Linq;
|
|
5
6
|
using Tags;
|
|
6
7
|
using UnityEngine;
|
|
7
8
|
using WallstopStudios.UnityHelpers.Utils;
|
|
@@ -39,14 +40,58 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
|
|
|
39
40
|
AttributeMetadataCache cache = _metadataCache ?? AttributeMetadataCache.Instance;
|
|
40
41
|
if (cache == null)
|
|
41
42
|
{
|
|
42
|
-
|
|
43
|
+
// Fallback to reflection-based discovery when no cache is available
|
|
44
|
+
return HasRelationalAttributesViaReflection(componentType);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
Type current = componentType;
|
|
48
|
+
while (current != null && typeof(Component).IsAssignableFrom(current))
|
|
49
|
+
{
|
|
50
|
+
if (
|
|
51
|
+
cache.TryGetRelationalFields(
|
|
52
|
+
current,
|
|
53
|
+
out AttributeMetadataCache.RelationalFieldMetadata[] fields
|
|
54
|
+
)
|
|
55
|
+
&& fields.Length > 0
|
|
48
56
|
)
|
|
49
|
-
|
|
57
|
+
{
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
current = current.BaseType;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fallback: inspect fields via reflection to detect relational attributes
|
|
64
|
+
return HasRelationalAttributesViaReflection(componentType);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private static bool HasRelationalAttributesViaReflection(Type componentType)
|
|
68
|
+
{
|
|
69
|
+
Type current = componentType;
|
|
70
|
+
while (current != null && typeof(Component).IsAssignableFrom(current))
|
|
71
|
+
{
|
|
72
|
+
// Prefer ReflectionHelpers so Editor TypeCache can accelerate lookups
|
|
73
|
+
bool has =
|
|
74
|
+
Helper
|
|
75
|
+
.ReflectionHelpers.GetFieldsWithAttribute<ParentComponentAttribute>(current)
|
|
76
|
+
.Any()
|
|
77
|
+
|| Helper
|
|
78
|
+
.ReflectionHelpers.GetFieldsWithAttribute<ChildComponentAttribute>(current)
|
|
79
|
+
.Any()
|
|
80
|
+
|| Helper
|
|
81
|
+
.ReflectionHelpers.GetFieldsWithAttribute<SiblingComponentAttribute>(
|
|
82
|
+
current
|
|
83
|
+
)
|
|
84
|
+
.Any();
|
|
85
|
+
|
|
86
|
+
if (has)
|
|
87
|
+
{
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
current = current.BaseType;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return false;
|
|
50
95
|
}
|
|
51
96
|
|
|
52
97
|
/// <inheritdoc />
|
|
@@ -17,6 +17,74 @@ namespace WallstopStudios.UnityHelpers.Core.Extension
|
|
|
17
17
|
/// </remarks>
|
|
18
18
|
public static class RandomExtensions
|
|
19
19
|
{
|
|
20
|
+
/// <summary>
|
|
21
|
+
/// Returns an index sampled from the provided weights (unnormalized). Negative weights are treated as zero.
|
|
22
|
+
/// </summary>
|
|
23
|
+
public static int NextWeightedIndex(this IRandom random, IReadOnlyList<float> weights)
|
|
24
|
+
{
|
|
25
|
+
if (weights == null)
|
|
26
|
+
{
|
|
27
|
+
throw new ArgumentNullException(nameof(weights));
|
|
28
|
+
}
|
|
29
|
+
if (weights.Count == 0)
|
|
30
|
+
{
|
|
31
|
+
throw new ArgumentException("Weights cannot be empty", nameof(weights));
|
|
32
|
+
}
|
|
33
|
+
double total = 0;
|
|
34
|
+
for (int i = 0; i < weights.Count; i++)
|
|
35
|
+
{
|
|
36
|
+
if (weights[i] > 0)
|
|
37
|
+
{
|
|
38
|
+
total += weights[i];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (total <= 0)
|
|
42
|
+
{
|
|
43
|
+
throw new ArgumentException("Sum of weights must be > 0", nameof(weights));
|
|
44
|
+
}
|
|
45
|
+
double r = random.NextDouble() * total;
|
|
46
|
+
double acc = 0;
|
|
47
|
+
for (int i = 0; i < weights.Count; i++)
|
|
48
|
+
{
|
|
49
|
+
float w = weights[i];
|
|
50
|
+
if (w <= 0)
|
|
51
|
+
{
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
acc += w;
|
|
55
|
+
if (r <= acc)
|
|
56
|
+
{
|
|
57
|
+
return i;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return weights.Count - 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// <summary>
|
|
64
|
+
/// Returns an element sampled according to the given weights list. Throws if lengths mismatch.
|
|
65
|
+
/// </summary>
|
|
66
|
+
public static T NextWeightedElement<T>(
|
|
67
|
+
this IRandom random,
|
|
68
|
+
IReadOnlyList<T> items,
|
|
69
|
+
IReadOnlyList<float> weights
|
|
70
|
+
)
|
|
71
|
+
{
|
|
72
|
+
if (items == null)
|
|
73
|
+
{
|
|
74
|
+
throw new ArgumentNullException(nameof(items));
|
|
75
|
+
}
|
|
76
|
+
if (weights == null)
|
|
77
|
+
{
|
|
78
|
+
throw new ArgumentNullException(nameof(weights));
|
|
79
|
+
}
|
|
80
|
+
if (items.Count != weights.Count)
|
|
81
|
+
{
|
|
82
|
+
throw new ArgumentException("Items and weights length must match.");
|
|
83
|
+
}
|
|
84
|
+
int idx = random.NextWeightedIndex(weights);
|
|
85
|
+
return items[idx];
|
|
86
|
+
}
|
|
87
|
+
|
|
20
88
|
/// <summary>
|
|
21
89
|
/// Generates a random 2D vector with components in the range [-amplitude, amplitude].
|
|
22
90
|
/// </summary>
|
|
@@ -74,6 +74,22 @@ namespace WallstopStudios.UnityHelpers.Core.Extension
|
|
|
74
74
|
LoggingEnabled = false;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/// <summary>
|
|
78
|
+
/// Gets whether global logging is enabled.
|
|
79
|
+
/// </summary>
|
|
80
|
+
public static bool IsGlobalLoggingEnabled()
|
|
81
|
+
{
|
|
82
|
+
return LoggingEnabled;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// <summary>
|
|
86
|
+
/// Sets global logging enabled/disabled without requiring an Object instance.
|
|
87
|
+
/// </summary>
|
|
88
|
+
public static void SetGlobalLoggingEnabled(bool enabled)
|
|
89
|
+
{
|
|
90
|
+
LoggingEnabled = enabled;
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
public static void EnableLogging(this Object component)
|
|
78
94
|
{
|
|
79
95
|
Disabled.Remove(component);
|
|
@@ -3,11 +3,14 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
3
3
|
using System;
|
|
4
4
|
using System.Collections.Generic;
|
|
5
5
|
using Extension;
|
|
6
|
-
using UnityEditor;
|
|
7
6
|
using UnityEngine;
|
|
8
7
|
using UnityEngine.SceneManagement;
|
|
9
8
|
using WallstopStudios.UnityHelpers.Utils;
|
|
10
9
|
using Object = UnityEngine.Object;
|
|
10
|
+
#if UNITY_EDITOR
|
|
11
|
+
using UnityEditor;
|
|
12
|
+
#endif
|
|
13
|
+
|
|
11
14
|
#if UNITY_EDITOR
|
|
12
15
|
using UnityEditor.SceneManagement;
|
|
13
16
|
#endif
|
|
@@ -12,7 +12,9 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
12
12
|
using System.Linq.Expressions;
|
|
13
13
|
using System.Reflection;
|
|
14
14
|
using System.Runtime.CompilerServices;
|
|
15
|
+
#if UNITY_EDITOR
|
|
15
16
|
using UnityEditor;
|
|
17
|
+
#endif
|
|
16
18
|
#if EMIT_DYNAMIC_IL
|
|
17
19
|
using System.Reflection.Emit;
|
|
18
20
|
#endif
|
|
@@ -50,10 +52,9 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
50
52
|
{
|
|
51
53
|
// Cache for type resolution by name
|
|
52
54
|
#if !SINGLE_THREADED
|
|
53
|
-
private static readonly
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
> TypeResolutionCache = new(StringComparer.Ordinal);
|
|
55
|
+
private static readonly ConcurrentDictionary<string, Type> TypeResolutionCache = new(
|
|
56
|
+
StringComparer.Ordinal
|
|
57
|
+
);
|
|
57
58
|
#else
|
|
58
59
|
private static readonly Dictionary<string, Type> TypeResolutionCache = new(
|
|
59
60
|
StringComparer.Ordinal
|
|
@@ -383,6 +384,17 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
383
384
|
return ListCreators.GetOrAdd(elementType, type => GetListCreator(type)).Invoke();
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
// Test helpers to avoid reflection in tests when asserting cache state
|
|
388
|
+
internal static bool IsFieldGetterCached(FieldInfo field)
|
|
389
|
+
{
|
|
390
|
+
return field != null && FieldGetterCache.ContainsKey(field);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
internal static bool IsFieldSetterCached(FieldInfo field)
|
|
394
|
+
{
|
|
395
|
+
return field != null && FieldSetterCache.ContainsKey(field);
|
|
396
|
+
}
|
|
397
|
+
|
|
386
398
|
/// <summary>
|
|
387
399
|
/// Builds a cached delegate that returns the value of a field as <see cref="object"/>.
|
|
388
400
|
/// Supports instance and static fields.
|
|
@@ -3713,7 +3725,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
3713
3725
|
#if UNITY_EDITOR
|
|
3714
3726
|
try
|
|
3715
3727
|
{
|
|
3716
|
-
TypeCache.TypeCollection list =
|
|
3728
|
+
TypeCache.TypeCollection list = TypeCache.GetTypesDerivedFrom<T>();
|
|
3717
3729
|
return list.Where(t =>
|
|
3718
3730
|
t != null && (includeAbstract || (t.IsClass && !t.IsAbstract))
|
|
3719
3731
|
);
|
|
@@ -3747,7 +3759,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
3747
3759
|
#if UNITY_EDITOR
|
|
3748
3760
|
try
|
|
3749
3761
|
{
|
|
3750
|
-
TypeCache.TypeCollection list =
|
|
3762
|
+
TypeCache.TypeCollection list = TypeCache.GetTypesDerivedFrom(baseType);
|
|
3751
3763
|
return list.Where(t =>
|
|
3752
3764
|
t != null && (includeAbstract || (t.IsClass && !t.IsAbstract))
|
|
3753
3765
|
);
|
|
@@ -3799,8 +3811,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
3799
3811
|
#if UNITY_EDITOR
|
|
3800
3812
|
try
|
|
3801
3813
|
{
|
|
3802
|
-
TypeCache.TypeCollection types =
|
|
3803
|
-
UnityEditor.TypeCache.GetTypesWithAttribute<TAttribute>();
|
|
3814
|
+
TypeCache.TypeCollection types = TypeCache.GetTypesWithAttribute<TAttribute>();
|
|
3804
3815
|
return types.Where(t =>
|
|
3805
3816
|
t != null && (includeAbstract || (t.IsClass && !t.IsAbstract))
|
|
3806
3817
|
);
|
|
@@ -3855,7 +3866,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
3855
3866
|
try
|
|
3856
3867
|
{
|
|
3857
3868
|
TypeCache.MethodCollection methods =
|
|
3858
|
-
|
|
3869
|
+
TypeCache.GetMethodsWithAttribute<TAttribute>();
|
|
3859
3870
|
IEnumerable<MethodInfo> filtered = methods;
|
|
3860
3871
|
if (within != null)
|
|
3861
3872
|
{
|
|
@@ -3892,7 +3903,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
3892
3903
|
try
|
|
3893
3904
|
{
|
|
3894
3905
|
TypeCache.FieldInfoCollection fields =
|
|
3895
|
-
|
|
3906
|
+
TypeCache.GetFieldsWithAttribute<TAttribute>();
|
|
3896
3907
|
IEnumerable<FieldInfo> filtered = fields;
|
|
3897
3908
|
if (within != null)
|
|
3898
3909
|
{
|
|
@@ -2,9 +2,11 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
4
|
using System.Collections.Concurrent;
|
|
5
|
-
using UnityEditor;
|
|
6
5
|
using UnityEngine;
|
|
7
6
|
using Utils;
|
|
7
|
+
#if UNITY_EDITOR
|
|
8
|
+
using UnityEditor;
|
|
9
|
+
#endif
|
|
8
10
|
|
|
9
11
|
/// <summary>
|
|
10
12
|
/// Thread-safe dispatcher that enqueues work to run on Unity's main thread.
|
|
@@ -96,5 +98,47 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
}
|
|
101
|
+
|
|
102
|
+
/// <summary>
|
|
103
|
+
/// Posts an action to run on the main thread and returns a Task that completes after execution.
|
|
104
|
+
/// </summary>
|
|
105
|
+
public System.Threading.Tasks.Task RunAsync(Action action)
|
|
106
|
+
{
|
|
107
|
+
var tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
|
|
108
|
+
RunOnMainThread(() =>
|
|
109
|
+
{
|
|
110
|
+
try
|
|
111
|
+
{
|
|
112
|
+
action();
|
|
113
|
+
tcs.SetResult(true);
|
|
114
|
+
}
|
|
115
|
+
catch (Exception ex)
|
|
116
|
+
{
|
|
117
|
+
tcs.SetException(ex);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return tcs.Task;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// <summary>
|
|
124
|
+
/// Posts a function to run on the main thread and returns its result via Task.
|
|
125
|
+
/// </summary>
|
|
126
|
+
public System.Threading.Tasks.Task<T> Post<T>(Func<T> func)
|
|
127
|
+
{
|
|
128
|
+
var tcs = new System.Threading.Tasks.TaskCompletionSource<T>();
|
|
129
|
+
RunOnMainThread(() =>
|
|
130
|
+
{
|
|
131
|
+
try
|
|
132
|
+
{
|
|
133
|
+
T result = func();
|
|
134
|
+
tcs.SetResult(result);
|
|
135
|
+
}
|
|
136
|
+
catch (Exception ex)
|
|
137
|
+
{
|
|
138
|
+
tcs.SetException(ex);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return tcs.Task;
|
|
142
|
+
}
|
|
99
143
|
}
|
|
100
144
|
}
|
|
@@ -26,11 +26,19 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization.JsonConverters
|
|
|
26
26
|
JsonSerializerOptions options
|
|
27
27
|
)
|
|
28
28
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
if (value == null)
|
|
30
|
+
{
|
|
31
|
+
writer.WriteNullValue();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
writer.WriteStartObject();
|
|
36
|
+
writer.WriteString("name", value.name);
|
|
37
|
+
// Use AssemblyQualifiedName to disambiguate type for diagnostics
|
|
38
|
+
writer.WriteString("type", value.GetType().AssemblyQualifiedName);
|
|
39
|
+
// Emit the actual Unity instance ID without transformation for correctness.
|
|
40
|
+
writer.WriteNumber("instanceId", value.GetInstanceID());
|
|
41
|
+
writer.WriteEndObject();
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
}
|
|
@@ -141,7 +141,7 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization.JsonConverters
|
|
|
141
141
|
#if UNITY_2022_2_OR_NEWER
|
|
142
142
|
uint num = value.refreshRateRatio.numerator;
|
|
143
143
|
uint den = value.refreshRateRatio.denominator;
|
|
144
|
-
int hz = den != 0 ? (int)
|
|
144
|
+
int hz = den != 0 ? (int)Math.Round((double)num / den) : 0;
|
|
145
145
|
writer.WriteNumber(RefreshRateProp, hz);
|
|
146
146
|
writer.WritePropertyName(RefreshRatioProp);
|
|
147
147
|
writer.WriteStartObject();
|
|
@@ -19,7 +19,7 @@ namespace WallstopStudios.UnityHelpers.Core.Serialization.JsonConverters
|
|
|
19
19
|
string typeName = reader.GetString();
|
|
20
20
|
return string.IsNullOrWhiteSpace(typeName)
|
|
21
21
|
? null
|
|
22
|
-
:
|
|
22
|
+
: Helper.ReflectionHelpers.TryResolveType(typeName);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
|