feat: new ValueProvider improvements and source generator.
This commit is contained in:
@@ -2,6 +2,7 @@ using Godot;
|
|||||||
using GodotHostTest.AetherBind;
|
using GodotHostTest.AetherBind;
|
||||||
using GodotHostTest.Game.Target;
|
using GodotHostTest.Game.Target;
|
||||||
using GodotHostTest.Interfaces;
|
using GodotHostTest.Interfaces;
|
||||||
|
using OwofGames.AetherBind;
|
||||||
using OwofGames.GodotLume;
|
using OwofGames.GodotLume;
|
||||||
using ServiceSource = GodotHostTest.AetherBind.ServiceSource;
|
using ServiceSource = GodotHostTest.AetherBind.ServiceSource;
|
||||||
|
|
||||||
@@ -13,7 +14,14 @@ public partial class LevelServiceSource : ServiceSource
|
|||||||
|
|
||||||
protected override void OnScopeCreated(IProvider serviceProvider)
|
protected override void OnScopeCreated(IProvider serviceProvider)
|
||||||
{
|
{
|
||||||
serviceProvider.Get<IValueSetter<ITargetCollector>>().Set(_targetSpawner);
|
OnScopeCreatedWithProvider(serviceProvider);
|
||||||
serviceProvider.Get<IValueSetter<ITargetSpawner>>().Set(_targetSpawner);
|
}
|
||||||
|
|
||||||
|
[WithProvider]
|
||||||
|
private void OnScopeCreated(IValueSetter<ITargetCollector> targetCollector,
|
||||||
|
IValueSetter<ITargetSpawner> targetSpawner)
|
||||||
|
{
|
||||||
|
targetCollector.Set(_targetSpawner);
|
||||||
|
targetSpawner.Set(_targetSpawner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using GodotHostTest.AetherBind;
|
|
||||||
using GodotHostTest.Helpers;
|
using GodotHostTest.Helpers;
|
||||||
using GodotHostTest.Interfaces;
|
using GodotHostTest.Interfaces;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -8,16 +7,15 @@ using R3;
|
|||||||
|
|
||||||
namespace GodotHostTest.Game.Projectile;
|
namespace GodotHostTest.Game.Projectile;
|
||||||
|
|
||||||
[Injected]
|
|
||||||
public partial class Projectile : Node2D
|
public partial class Projectile : Node2D
|
||||||
{
|
{
|
||||||
[Inject] private ILogger<Projectile> _logger = null!;
|
[Inject] private ILogger<Projectile> _logger = null!;
|
||||||
[Inject] private IValueGetter<ITargetSpawner> _targetSpawner = null!;
|
[Inject] private ITargetSpawner _targetSpawner = null!;
|
||||||
|
|
||||||
// Called when the node enters the scene tree for the first time.
|
// Called when the node enters the scene tree for the first time.
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_targetSpawner.Value.TargetSpawned
|
_targetSpawner.TargetSpawned
|
||||||
.Subscribe(_ => _logger.LogInformation("Projectile got informed of target spawning")).AddTo(this);
|
.Subscribe(_ => _logger.LogInformation("Projectile got informed of target spawning")).AddTo(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-8
@@ -1,13 +1,10 @@
|
|||||||
using System;
|
|
||||||
using Godot;
|
using Godot;
|
||||||
using GodotHostTest.AetherBind;
|
using GodotHostTest.AetherBind;
|
||||||
using GodotHostTest.Interfaces;
|
using GodotHostTest.Interfaces;
|
||||||
using OwofGames.AetherBind;
|
using OwofGames.AetherBind;
|
||||||
using OwofGames.GodotLume;
|
|
||||||
|
|
||||||
namespace GodotHostTest.Game.Root;
|
namespace GodotHostTest.Game.Root;
|
||||||
|
|
||||||
[Injected]
|
|
||||||
public partial class Root : Node2D
|
public partial class Root : Node2D
|
||||||
{
|
{
|
||||||
[Inject] private ICurrentLevel _currentLevel = null!;
|
[Inject] private ICurrentLevel _currentLevel = null!;
|
||||||
@@ -17,11 +14,13 @@ public partial class Root : Node2D
|
|||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
var levelData = _levelDataProvider.GetLevelData(_currentLevel.LevelNumber);
|
AddChild(_sceneInstantiator.Instantiate(_levelScene, OnInstantiateWithProvider));
|
||||||
AddChild(_sceneInstantiator.Instantiate(_levelScene,
|
|
||||||
OnInstantiate(levelValueSetter => levelValueSetter.Set(levelData))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceMethod]
|
[WithProvider]
|
||||||
private partial Action<IProvider> OnInstantiate(Action<IValueSetter<ILevel>> a);
|
private void OnInstantiate(IValueSetter<ILevel> levelValueSetter)
|
||||||
|
{
|
||||||
|
var levelData = _levelDataProvider.GetLevelData(_currentLevel.LevelNumber);
|
||||||
|
levelValueSetter.Set(levelData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
-4
@@ -1,5 +1,4 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using GodotHostTest.AetherBind;
|
|
||||||
using GodotHostTest.Helpers;
|
using GodotHostTest.Helpers;
|
||||||
using GodotHostTest.Interfaces;
|
using GodotHostTest.Interfaces;
|
||||||
using OwofGames.AetherBind;
|
using OwofGames.AetherBind;
|
||||||
@@ -7,20 +6,19 @@ using R3;
|
|||||||
|
|
||||||
namespace GodotHostTest.Game.Score;
|
namespace GodotHostTest.Game.Score;
|
||||||
|
|
||||||
[Injected]
|
|
||||||
public partial class Score : Control, IScore
|
public partial class Score : Control, IScore
|
||||||
{
|
{
|
||||||
[Export] private AnimationPlayer _animationPlayer = null!;
|
[Export] private AnimationPlayer _animationPlayer = null!;
|
||||||
[Export] private Label _label = null!;
|
[Export] private Label _label = null!;
|
||||||
|
|
||||||
private int _score;
|
private int _score;
|
||||||
[Inject] private IValueGetter<ITargetSpawner> _targetSpawner = null!;
|
[Inject] private ITargetSpawner _targetSpawner = null!;
|
||||||
|
|
||||||
int IScore.Score => _score;
|
int IScore.Score => _score;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_targetSpawner.Value.TargetSpawned.Subscribe(OnTargetSpawned).AddTo(this);
|
_targetSpawner.TargetSpawned.Subscribe(OnTargetSpawned).AddTo(this);
|
||||||
base._Ready();
|
base._Ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using GodotHostTest.AetherBind;
|
|
||||||
using GodotHostTest.Interfaces;
|
using GodotHostTest.Interfaces;
|
||||||
using OwofGames.AetherBind;
|
using OwofGames.AetherBind;
|
||||||
using R3;
|
using R3;
|
||||||
|
|
||||||
namespace GodotHostTest.Game.Target;
|
namespace GodotHostTest.Game.Target;
|
||||||
|
|
||||||
[Injected]
|
|
||||||
public partial class Target : Node2D, ITarget
|
public partial class Target : Node2D, ITarget
|
||||||
{
|
{
|
||||||
private readonly Subject<Unit> _hit = new();
|
private readonly Subject<Unit> _hit = new();
|
||||||
@@ -17,7 +15,7 @@ public partial class Target : Node2D, ITarget
|
|||||||
[Export] public double TimeOutDurationInSeconds;
|
[Export] public double TimeOutDurationInSeconds;
|
||||||
[Export] public required Timer TimeOutTimer;
|
[Export] public required Timer TimeOutTimer;
|
||||||
|
|
||||||
[Inject] private IValueGetter<ITargetCollector> _targetCollector = null!;
|
[Inject] private ITargetCollector _targetCollector = null!;
|
||||||
|
|
||||||
public Observable<Unit> TimedOut => _timedOut.AsObservable();
|
public Observable<Unit> TimedOut => _timedOut.AsObservable();
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ public partial class Target : Node2D, ITarget
|
|||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_targetCollector.Value.NewTarget(this);
|
_targetCollector.NewTarget(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
public override void _ExitTree()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||||||
using Godot;
|
using Godot;
|
||||||
using GodotHostTest.Helpers;
|
using GodotHostTest.Helpers;
|
||||||
using GodotHostTest.Interfaces;
|
using GodotHostTest.Interfaces;
|
||||||
|
using OwofGames.AetherBind;
|
||||||
using R3;
|
using R3;
|
||||||
|
|
||||||
namespace GodotHostTest.Game.Target;
|
namespace GodotHostTest.Game.Target;
|
||||||
@@ -11,10 +12,10 @@ public partial class TargetSpawner : Node2D, ITargetCollector, ITargetSpawner
|
|||||||
private readonly List<ITarget> _availableTargets = [];
|
private readonly List<ITarget> _availableTargets = [];
|
||||||
|
|
||||||
private readonly Subject<ITarget> _targetSpawned = new();
|
private readonly Subject<ITarget> _targetSpawned = new();
|
||||||
[Export] public double TimeBetweenSpawns;
|
|
||||||
|
|
||||||
[Export] public required Timer Timer;
|
[Export] public required Timer Timer;
|
||||||
|
|
||||||
|
[Inject] private ILevel _level = null!;
|
||||||
|
|
||||||
public void NewTarget(ITarget target)
|
public void NewTarget(ITarget target)
|
||||||
{
|
{
|
||||||
_availableTargets.Add(target);
|
_availableTargets.Add(target);
|
||||||
@@ -26,7 +27,7 @@ public partial class TargetSpawner : Node2D, ITargetCollector, ITargetSpawner
|
|||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
Spawn();
|
Spawn();
|
||||||
Timer.Start(TimeBetweenSpawns);
|
Timer.Start(_level.TimeBetweenSpawns.TotalSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MakeTargetAvailable(ITarget target)
|
private void MakeTargetAvailable(ITarget target)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using OwofGames.AetherBind;
|
|||||||
|
|
||||||
namespace GodotHostTest.Game.Turret;
|
namespace GodotHostTest.Game.Turret;
|
||||||
|
|
||||||
[Injected]
|
|
||||||
public partial class Turret : Sprite2D
|
public partial class Turret : Sprite2D
|
||||||
{
|
{
|
||||||
[Export] private PackedScene _projectilePackedScene = null!;
|
[Export] private PackedScene _projectilePackedScene = null!;
|
||||||
|
|||||||
+1
-2
@@ -74,8 +74,8 @@ _data = {
|
|||||||
[node name="LevelScope" type="Node" parent="." unique_id=1267156771 node_paths=PackedStringArray("_targetSpawner", "InjectedNodes")]
|
[node name="LevelScope" type="Node" parent="." unique_id=1267156771 node_paths=PackedStringArray("_targetSpawner", "InjectedNodes")]
|
||||||
script = ExtResource("1_5saw1")
|
script = ExtResource("1_5saw1")
|
||||||
_targetSpawner = NodePath("../Spawner")
|
_targetSpawner = NodePath("../Spawner")
|
||||||
|
InjectedNodes = [NodePath("../Target"), NodePath("../Target2"), NodePath("../Target3"), NodePath("../Target4"), NodePath("../Target5"), NodePath("../Target6"), NodePath("../Score"), NodePath("../Turret"), NodePath("../Spawner")]
|
||||||
_serviceScope = "GodotHostTest.Game.Level.LevelScope, godot-host-test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
_serviceScope = "GodotHostTest.Game.Level.LevelScope, godot-host-test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||||
InjectedNodes = [NodePath("../Target"), NodePath("../Target2"), NodePath("../Target3"), NodePath("../Target4"), NodePath("../Target5"), NodePath("../Target6"), NodePath("../Score"), NodePath("../Turret")]
|
|
||||||
|
|
||||||
[node name="Target" parent="." unique_id=2101558052 instance=ExtResource("1_y7rhs")]
|
[node name="Target" parent="." unique_id=2101558052 instance=ExtResource("1_y7rhs")]
|
||||||
position = Vector2(198, 143)
|
position = Vector2(198, 143)
|
||||||
@@ -103,7 +103,6 @@ TimeOutDurationInSeconds = 2.0
|
|||||||
|
|
||||||
[node name="Spawner" type="Node2D" parent="." unique_id=251404460 node_paths=PackedStringArray("Timer")]
|
[node name="Spawner" type="Node2D" parent="." unique_id=251404460 node_paths=PackedStringArray("Timer")]
|
||||||
script = ExtResource("2_5saw1")
|
script = ExtResource("2_5saw1")
|
||||||
TimeBetweenSpawns = 1.5
|
|
||||||
Timer = NodePath("Timer")
|
Timer = NodePath("Timer")
|
||||||
|
|
||||||
[node name="Timer" type="Timer" parent="Spawner" unique_id=1066078427]
|
[node name="Timer" type="Timer" parent="Spawner" unique_id=1066078427]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_bbjqm"]
|
[sub_resource type="Resource" id="Resource_bbjqm"]
|
||||||
script = ExtResource("1_bkfnq")
|
script = ExtResource("1_bkfnq")
|
||||||
TimeBetweenSpawns = 2.0
|
TimeBetweenSpawns = 0.5
|
||||||
metadata/_custom_type_script = "uid://ywe7vkywrdvk"
|
metadata/_custom_type_script = "uid://ywe7vkywrdvk"
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
|
|||||||
@@ -5,18 +5,25 @@ namespace GodotHostTest.AetherBind;
|
|||||||
public static class BuilderExtensions
|
public static class BuilderExtensions
|
||||||
{
|
{
|
||||||
public static Builder AddValueProvider<TValueType, TSourceScope, TDestinationScope>(this Builder builder)
|
public static Builder AddValueProvider<TValueType, TSourceScope, TDestinationScope>(this Builder builder)
|
||||||
|
where TValueType : class
|
||||||
where TSourceScope : RootScope
|
where TSourceScope : RootScope
|
||||||
where TDestinationScope : TSourceScope
|
where TDestinationScope : TSourceScope
|
||||||
{
|
{
|
||||||
var valueProvider = new ValueProvider<TValueType>();
|
var valueProvider = new ValueProvider<TValueType>();
|
||||||
return builder
|
return builder
|
||||||
|
// add the setter to the source scope
|
||||||
.AddScoped<IValueSetter<TValueType>, IValueSetter<TValueType>, TSourceScope>(Builder.NoDependencies,
|
.AddScoped<IValueSetter<TValueType>, IValueSetter<TValueType>, TSourceScope>(Builder.NoDependencies,
|
||||||
_ => valueProvider.Setter)
|
_ => valueProvider.Setter)
|
||||||
|
// add the getter to the destination scope
|
||||||
.AddScoped<IValueGetter<TValueType>, IValueGetter<TValueType>, TDestinationScope>(Builder.NoDependencies,
|
.AddScoped<IValueGetter<TValueType>, IValueGetter<TValueType>, TDestinationScope>(Builder.NoDependencies,
|
||||||
_ => valueProvider.Getter);
|
_ => valueProvider.Getter)
|
||||||
|
// add a factory to automatically extract TValueType from the IValueGetter<TValueType>
|
||||||
|
.AddScoped<TValueType, TValueType, TDestinationScope>(_ => [typeof(IValueGetter<TValueType>)],
|
||||||
|
provider => provider.Get<IValueGetter<TValueType>>().Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder AddValueProvider<TValueType, TScope>(this Builder builder)
|
public static Builder AddValueProvider<TValueType, TScope>(this Builder builder)
|
||||||
|
where TValueType : class
|
||||||
where TScope : RootScope
|
where TScope : RootScope
|
||||||
{
|
{
|
||||||
return builder.AddValueProvider<TValueType, TScope, TScope>();
|
return builder.AddValueProvider<TValueType, TScope, TScope>();
|
||||||
|
|||||||
@@ -15,4 +15,31 @@ TOC:
|
|||||||
- deep dive into why it's all done at composition root
|
- deep dive into why it's all done at composition root
|
||||||
- using extension methods to split the code as necessary
|
- using extension methods to split the code as necessary
|
||||||
- sending data into a scope
|
- sending data into a scope
|
||||||
- initializing data in the scope
|
- initializing data in the scope
|
||||||
|
|
||||||
|
NOTES
|
||||||
|
|
||||||
|
field with \[Inject\] or method with \[Inject\] automatically receive injection IF they are between the injected objects
|
||||||
|
of a ServiceSource
|
||||||
|
|
||||||
|
a method with \[WithProvider\] generates a companion method for unwrapping provider. E.g.:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[WithProvider]
|
||||||
|
private void OnReady(IService1 service1, IService2 service2)
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
will generate a companion method similar to:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private void OnReadyWithProvider(IProvider provider)
|
||||||
|
{
|
||||||
|
OnReady(
|
||||||
|
provider.Get<IService1>(),
|
||||||
|
provider.Get<IService2>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -3,13 +3,14 @@ using System;
|
|||||||
namespace GodotHostTest.AetherBind;
|
namespace GodotHostTest.AetherBind;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An object that allows communication through scopes, by providing a couple getter/setter that can be injected in
|
/// An object that allows communication through scopes (or inside the scope itself) by providing a couple getter/setter that can be injected in different scopes (or the same scope).
|
||||||
/// different scopes.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type that's been passed through scopes.</typeparam>
|
/// <typeparam name="T">The type that's been passed through scopes.</typeparam>
|
||||||
/// <seealso cref="BuilderExtensions.AddValueProvider" />
|
/// <seealso cref="BuilderExtensions.AddValueProvider{TValueType,TScope}" />
|
||||||
|
/// <seealso cref="BuilderExtensions.AddValueProvider{TValueType,TSourceScope,TDestinationScope}" />
|
||||||
public class ValueProvider<T>
|
public class ValueProvider<T>
|
||||||
{
|
{
|
||||||
|
private IValueGetter<T>? _getter;
|
||||||
private bool _isSet;
|
private bool _isSet;
|
||||||
private T? _value;
|
private T? _value;
|
||||||
|
|
||||||
@@ -20,9 +21,16 @@ public class ValueProvider<T>
|
|||||||
|
|
||||||
public IValueSetter<T> Setter { get; }
|
public IValueSetter<T> Setter { get; }
|
||||||
|
|
||||||
public IValueGetter<T> Getter => !_isSet
|
public IValueGetter<T> Getter
|
||||||
? throw new InvalidOperationException($"Value of type {typeof(T)} has not been set yet.")
|
{
|
||||||
: new GetterImplementation(_value!);
|
get
|
||||||
|
{
|
||||||
|
_getter ??= !_isSet
|
||||||
|
? throw new InvalidOperationException($"Value of type {typeof(T)} has not been set yet.")
|
||||||
|
: new GetterImplementation(_value!);
|
||||||
|
return _getter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class SetterImplementation(ValueProvider<T> valueProvider) : IValueSetter<T>
|
private class SetterImplementation(ValueProvider<T> valueProvider) : IValueSetter<T>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user