Skip to content

Commit

Permalink
feat: Add WeakReference to store reference types in a default provider
Browse files Browse the repository at this point in the history
  • Loading branch information
CoreyAlexander committed Dec 17, 2024
1 parent 68c490d commit f8e4643
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public static TValue DependOn<TValue>(
// all the other dependency resolution methods.
var state = dependent.MixinState.Get<DependentState>();
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
Expand All @@ -146,7 +146,7 @@ public static TValue DependOn<TValue>(
return provider.Value();
}
else if (providerNode is DefaultProvider defaultProvider) {
return defaultProvider.Value();
return (TValue)defaultProvider.Value();
}
}
else if (fallback is not null) {
Expand Down Expand Up @@ -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<object>(value);
}
ProviderState = new() { IsInitialized = true };
}

public dynamic Value() => _value;
public object Value() {
if (_value is WeakReference<object> 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;
}
}
}
}
19 changes: 19 additions & 0 deletions Chickensoft.AutoInject.Tests/test/fixtures/Dependents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 15 additions & 1 deletion Chickensoft.AutoInject.Tests/test/src/ResolutionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void ThrowsWhenNoProviderFound() {
}

[Test]
public void UsesFallbackValueWhenNoProviderFound() {
public void UsesValueFallbackValueWhenNoProviderFound() {
var fallback = "Hello, world!";
var dependent = new StringDependentFallback {
FallbackValue = fallback
Expand All @@ -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!";
Expand Down

0 comments on commit f8e4643

Please sign in to comment.