feat: new ValueProvider improvements and source generator.

This commit is contained in:
redglow
2026-07-20 11:06:35 +02:00
parent 036e4af431
commit c540b99eb6
12 changed files with 79 additions and 37 deletions
+8 -1
View File
@@ -5,18 +5,25 @@ namespace GodotHostTest.AetherBind;
public static class BuilderExtensions
{
public static Builder AddValueProvider<TValueType, TSourceScope, TDestinationScope>(this Builder builder)
where TValueType : class
where TSourceScope : RootScope
where TDestinationScope : TSourceScope
{
var valueProvider = new ValueProvider<TValueType>();
return builder
// add the setter to the source scope
.AddScoped<IValueSetter<TValueType>, IValueSetter<TValueType>, TSourceScope>(Builder.NoDependencies,
_ => valueProvider.Setter)
// add the getter to the destination scope
.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)
where TValueType : class
where TScope : RootScope
{
return builder.AddValueProvider<TValueType, TScope, TScope>();
+28 -1
View File
@@ -15,4 +15,31 @@ TOC:
- deep dive into why it's all done at composition root
- using extension methods to split the code as necessary
- 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>()
);
}
```
+14 -6
View File
@@ -3,13 +3,14 @@ using System;
namespace GodotHostTest.AetherBind;
/// <summary>
/// An object that allows communication through scopes, by providing a couple getter/setter that can be injected in
/// different scopes.
/// 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).
/// </summary>
/// <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>
{
private IValueGetter<T>? _getter;
private bool _isSet;
private T? _value;
@@ -20,9 +21,16 @@ public class ValueProvider<T>
public IValueSetter<T> Setter { get; }
public IValueGetter<T> Getter => !_isSet
? throw new InvalidOperationException($"Value of type {typeof(T)} has not been set yet.")
: new GetterImplementation(_value!);
public IValueGetter<T> Getter
{
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>
{