diff --git a/Chickensoft.AutoInject.Tests/src/auto_inject/dependent/DependencyResolver.cs b/Chickensoft.AutoInject.Tests/src/auto_inject/dependent/DependencyResolver.cs index f7520aa..fe3a5d6 100644 --- a/Chickensoft.AutoInject.Tests/src/auto_inject/dependent/DependencyResolver.cs +++ b/Chickensoft.AutoInject.Tests/src/auto_inject/dependent/DependencyResolver.cs @@ -129,7 +129,7 @@ public static TValue DependOn( // all the other dependency resolution methods. var state = dependent.MixinState.Get(); if (state.ProviderFakes.TryGetValue(typeof(TValue), out var fakeProvider)) { - return fakeProvider.Value(); + return (TValue)fakeProvider.Value(); } // Lookup dependency, per usual, respecting any fallback values if there @@ -146,7 +146,7 @@ public static TValue DependOn( return provider.Value(); } else if (providerNode is DefaultProvider defaultProvider) { - return defaultProvider.Value(); + return (TValue)defaultProvider.Value(); } } else if (fallback is not null) { @@ -284,14 +284,41 @@ void onProviderInitialized(IBaseProvider provider) { } public class DefaultProvider : IBaseProvider { - private readonly dynamic _value; + private readonly object _value; public ProviderState ProviderState { get; } - public DefaultProvider(dynamic value) { - _value = value; + // When working with reference types, we must wrap the value in a WeakReference<>() + // to allow the garbage collection to work when the assembly is being unloaded or reloaded; + // such as in the case of rebuilding within the Godot Editor. + public DefaultProvider(object value) { + // Ensure value is never null and is a valid reference type when required + if (value == null) { + throw new ArgumentNullException(nameof(value), "Value cannot be null."); + } + + if (value.GetType().IsValueType) { + _value = value; + } else { + _value = new WeakReference(value); + } ProviderState = new() { IsInitialized = true }; } - public dynamic Value() => _value; + public object Value() { + if (_value is WeakReference weakReference) { + if (weakReference.TryGetTarget(out var target)) { + //TODO: What should happen in the case that a weak reference + // has been GC'd but the default provider is still here? + // should it clean up the old reference and make a new one? + // who would benefit from the thrown error? + return target; + } + throw new ObjectDisposedException( + "The reference object has been garbage collected" + ); + } else { + return _value; + } + } } } diff --git a/Chickensoft.AutoInject.Tests/test/fixtures/Dependents.cs b/Chickensoft.AutoInject.Tests/test/fixtures/Dependents.cs index 589b7bd..7cd24ac 100644 --- a/Chickensoft.AutoInject.Tests/test/fixtures/Dependents.cs +++ b/Chickensoft.AutoInject.Tests/test/fixtures/Dependents.cs @@ -56,6 +56,25 @@ public void OnResolved() { } } +[Meta(typeof(IAutoOn), typeof(IDependent))] +public partial class ReferenceDependentFallback : Node { + public override void _Notification(int what) => this.Notify(what); + + [Dependency] + public object MyDependency => this.DependOn(() => FallbackValue); + + public object FallbackValue { get; set; } = null!; + public bool OnResolvedCalled { get; private set; } + public object ResolvedValue { get; set; } = null!; + + public void OnReady() { } + + public void OnResolved() { + OnResolvedCalled = true; + ResolvedValue = MyDependency; + } +} + [Meta(typeof(IAutoOn), typeof(IDependent))] public partial class IntDependent : Node { public override void _Notification(int what) => this.Notify(what); diff --git a/Chickensoft.AutoInject.Tests/test/src/ResolutionTest.cs b/Chickensoft.AutoInject.Tests/test/src/ResolutionTest.cs index b5e825f..50185e7 100644 --- a/Chickensoft.AutoInject.Tests/test/src/ResolutionTest.cs +++ b/Chickensoft.AutoInject.Tests/test/src/ResolutionTest.cs @@ -131,7 +131,7 @@ public void ThrowsWhenNoProviderFound() { } [Test] - public void UsesFallbackValueWhenNoProviderFound() { + public void UsesValueFallbackValueWhenNoProviderFound() { var fallback = "Hello, world!"; var dependent = new StringDependentFallback { FallbackValue = fallback @@ -142,6 +142,20 @@ public void UsesFallbackValueWhenNoProviderFound() { dependent.ResolvedValue.ShouldBe(fallback); dependent.MyDependency.ShouldBe(fallback); } + + [Test] + public void UsesReferenceFallbackValueWhenNoProviderFound() { + var fallback = new Resource(); + var dependent = new ReferenceDependentFallback { + FallbackValue = fallback + }; + + dependent._Notification((int)Node.NotificationReady); + + dependent.ResolvedValue.ShouldBe(fallback); + dependent.MyDependency.ShouldBe(fallback); + } + [Test] public void ThrowsOnDependencyTableThatWasTamperedWith() { var fallback = "Hello, world!";