feat: automatic injection of elements in scene.

This commit is contained in:
redglow
2026-07-17 15:14:40 +02:00
parent 659baff30c
commit 5ad3af84b2
15 changed files with 86 additions and 219 deletions
@@ -1,11 +1,13 @@
#if TOOLS
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Godot;
namespace GodotHostTest.addons.godot_di;
namespace GodotHostTest.GodotDI.Editor;
/// <summary>
/// Taken from https://github.com/godotengine/godot-proposals/issues/12294
@@ -23,7 +25,7 @@ public static class GodotScriptPathCache
{
if (_assemblyLoadEventHandler != null) return;
Initialize();
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())!
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())!
.Unloading += _ => { Deinitialize(); };
}
@@ -87,4 +89,21 @@ public static class GodotScriptPathCache
InitializeIfNeeded();
return PathToType.TryGetValue(path, out type);
}
}
public static Type? GetCSharpScriptType(this GodotObject node)
{
if (node.GetScript().AsGodotObject() is CSharpScript script &&
TryGetTypeFromPath(script.ResourcePath, out var type))
return type;
return null;
}
public static bool IsServiceSource(this GodotObject node)
{
return node.GetScript().AsGodotObject() is CSharpScript script &&
TryGetTypeFromPath(script.ResourcePath, out var type) &&
type.IsAssignableTo(typeof(ServiceSource));
}
}
#endif
@@ -1,16 +1,12 @@
#if TOOLS
using System;
using System.Linq;
using Godot;
namespace GodotHostTest.GodotDI;
namespace GodotHostTest.GodotDI.Editor;
public partial class ScopePickerEditor : EditorProperty
{
/// <summary>
/// The "combo box" that picks a choice from the scopes.
/// </summary>
private OptionButton _optionButton = new();
/// <summary>
/// The currently chosen type.
/// </summary>
@@ -21,6 +17,11 @@ public partial class ScopePickerEditor : EditorProperty
/// </summary>
private string[] _fullyQualifiedNames;
/// <summary>
/// The "combo box" that picks a choice from the scopes.
/// </summary>
private OptionButton _optionButton = new();
/// <summary>
/// A guard against internal changes while the property is being updated.
/// </summary>
@@ -92,4 +93,5 @@ public partial class ScopePickerEditor : EditorProperty
{
_optionButton.Selected = Array.IndexOf(_fullyQualifiedNames, _currentType);
}
}
}
#endif
+33
View File
@@ -0,0 +1,33 @@
using System.Collections.Generic;
namespace GodotHostTest.GodotDI;
public static class EnumerableExtensions
{
public enum SingleFailureReason
{
LessThanOne,
MoreThanOne
}
public static T? Single<T>(this IEnumerable<T> enumerable, out SingleFailureReason? failureReason)
where T : class
{
using var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
{
failureReason = SingleFailureReason.LessThanOne;
return null;
}
var value = enumerator.Current;
if (enumerator.MoveNext())
{
failureReason = SingleFailureReason.MoreThanOne;
return null;
}
failureReason = null;
return value;
}
}
-64
View File
@@ -1,64 +0,0 @@
namespace GodotHostTest.addons.godot_di;
// RandomIntEditor.cs
#if TOOLS
using Godot;
public partial class RandomIntEditor : EditorProperty
{
// The main control for editing the property.
private Button _propertyControl = new Button();
// An internal value of the property.
private int _currentValue = 0;
// A guard against internal changes when the property is updated.
private bool _updating = false;
public RandomIntEditor()
{
// Add the control as a direct child of EditorProperty node.
AddChild(_propertyControl);
// Make sure the control is able to retain the focus.
AddFocusable(_propertyControl);
// Setup the initial state and connect to the signal to track changes.
RefreshControlText();
_propertyControl.Pressed += OnButtonPressed;
}
private void OnButtonPressed()
{
// Ignore the signal if the property is currently being updated.
if (_updating)
{
return;
}
// Generate a new random integer between 0 and 99.
_currentValue = (int)GD.Randi() % 100;
RefreshControlText();
EmitChanged(GetEditedProperty(), _currentValue);
}
public override void _UpdateProperty()
{
// Read the current value from the property.
var newValue = (int)GetEditedObject().Get(GetEditedProperty());
if (newValue == _currentValue)
{
return;
}
// Update the control with the new value.
_updating = true;
_currentValue = newValue;
RefreshControlText();
_updating = false;
}
private void RefreshControlText()
{
_propertyControl.Text = $"Value: {_currentValue}";
}
}
#endif
-1
View File
@@ -1 +0,0 @@
uid://db526cjmnd4q7
+10 -5
View File
@@ -9,17 +9,22 @@ namespace GodotHostTest.GodotDI;
[Icon("res://addons/GodotDI/ServiceSource.svg")]
public partial class ServiceSource : Node
{
[Export] private string _serviceScope = typeof(RootScope).AssemblyQualifiedName!;
private static readonly string RootScopeQualifiedName = typeof(RootScope).AssemblyQualifiedName!;
[Export] private string _serviceScope = RootScopeQualifiedName;
[Export] protected Node?[] InjectedNodes = [];
internal void ResolveInjectedNodes(IServiceProvider serviceProvider)
{
// if this service source must also act as a scope, create the scope and use its service provider
if (_serviceScope != "")
if (_serviceScope != RootScopeQualifiedName)
{
serviceProvider = serviceProvider
.CreateScope()
.ServiceProvider;
var serviceProviderCreator = serviceProvider.GetRequiredService<Func<IProvider, IServiceProvider>>();
var scopedProvider = serviceProvider.GetRequiredService<IProvider>()
.GetScopedProvider(ScopeTypesAndNames.GetType(_serviceScope));
serviceProvider = serviceProviderCreator(scopedProvider);
// serviceProvider = serviceProvider
// .CreateScope()
// .ServiceProvider;
}
// resolve the injected nodes against the chosen service provider
+1 -1
View File
@@ -4,4 +4,4 @@ name="GodotDI"
description=""
author="owof games"
version="0.1"
script="GodotDI.cs"
script="Editor/Plugin.cs"