diff --git a/Game/GameRootScope.cs b/Game/GameRootServiceSource.cs similarity index 87% rename from Game/GameRootScope.cs rename to Game/GameRootServiceSource.cs index a61378b..837120d 100644 --- a/Game/GameRootScope.cs +++ b/Game/GameRootServiceSource.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; namespace GodotHostTest.Game; -public partial class GameRootScope : RootScope +public partial class GameRootServiceSource : RootServiceSource { [Export] private TargetSpawner _targetSpawner = null!; diff --git a/Game/GameRootScope.cs.uid b/Game/GameRootServiceSource.cs.uid similarity index 100% rename from Game/GameRootScope.cs.uid rename to Game/GameRootServiceSource.cs.uid diff --git a/Game/LevelScope.cs b/Game/LevelScope.cs new file mode 100644 index 0000000..3b57c41 --- /dev/null +++ b/Game/LevelScope.cs @@ -0,0 +1,5 @@ +using OwofGames.GodotLume; + +namespace GodotHostTest.Game; + +public class LevelScope: RootScope; \ No newline at end of file diff --git a/Game/Score.cs b/Game/Score.cs index adff9b6..58e2522 100644 --- a/Game/Score.cs +++ b/Game/Score.cs @@ -6,7 +6,7 @@ using GodotHostTest.Interfaces; namespace GodotHostTest.Game; -public partial class Score : Control +public partial class Score : Control, IScore { [Export] private AnimationPlayer _animationPlayer = null!; [Export] private Label _label = null!; @@ -34,4 +34,6 @@ public partial class Score : Control { _label.Text = $"Score: {_score}"; } + + int IScore.Score => _score; } \ No newline at end of file diff --git a/Game/projectile.tscn b/Game/projectile.tscn index 38959d4..127de57 100644 --- a/Game/projectile.tscn +++ b/Game/projectile.tscn @@ -1,7 +1,7 @@ [gd_scene format=3 uid="uid://brbufibn7hi3o"] [ext_resource type="Script" uid="uid://dc5mvle8lc40j" path="res://Game/Projectile.cs" id="1_80yud"] -[ext_resource type="Script" uid="uid://qjonl8stia46" path="res://GodotDI/Scope.cs" id="2_3kmdq"] +[ext_resource type="Script" uid="uid://qjonl8stia46" path="res://addons/GodotDI/ServiceSource.cs" id="2_3kmdq"] [ext_resource type="Texture2D" uid="uid://y1ros3y7hag2" path="res://icon.svg" id="3_1i54g"] [node name="Projectile" type="Node2D" unique_id=1686616504] diff --git a/GodotDI/InjectAttribute.cs b/GodotDI/InjectAttribute.cs deleted file mode 100644 index 6d3c159..0000000 --- a/GodotDI/InjectAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace GodotHostTest.GodotDI; - -[AttributeUsage(AttributeTargets.Field)] -public class InjectAttribute: Attribute; \ No newline at end of file diff --git a/GodotDI/Scope.cs b/GodotDI/Scope.cs deleted file mode 100644 index 102f460..0000000 --- a/GodotDI/Scope.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Reflection; -using Godot; -using Microsoft.Extensions.DependencyInjection; - -namespace GodotHostTest.GodotDI; - -public partial class Scope : Node -{ - [Export] protected Node?[] InjectedNodes = []; - - internal void ResolveInjectedNodes(IServiceProvider serviceProvider) - { - // resolve the injected nodes - // TODO: move to source generator, make it the only way for injection - foreach (var node in InjectedNodes) - { - if (node == null) continue; - foreach (var field in node.GetType() - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - { - if (field.GetCustomAttribute() == null) continue; - var fieldType = field.FieldType; - var service = serviceProvider.GetRequiredService(fieldType); - field.SetValue(node, service); - } - } - } -} \ No newline at end of file diff --git a/Interfaces/IScore.cs b/Interfaces/IScore.cs index 0883176..377bf84 100644 --- a/Interfaces/IScore.cs +++ b/Interfaces/IScore.cs @@ -5,16 +5,6 @@ namespace GodotHostTest.Interfaces; /// public interface IScore { - /// - /// A target has been hit. - /// - void TargetHit(); - - /// - /// A target has timed out. - /// - void TargetTimedOut(); - /// /// The current score. /// diff --git a/addons/GodotDI/GodotScriptPathCache.cs b/addons/GodotDI/GodotScriptPathCache.cs new file mode 100644 index 0000000..2465221 --- /dev/null +++ b/addons/GodotDI/GodotScriptPathCache.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using Godot; + +namespace GodotHostTest.addons.godot_di; + +/// +/// Taken from https://github.com/godotengine/godot-proposals/issues/12294 +/// +public static class GodotScriptPathCache +{ + private static readonly ConcurrentDictionary TypeToPath = new(); + private static readonly ConcurrentDictionary PathToType = new(); + private static readonly Type GodotObject = typeof(GodotObject); + private static readonly Type ScriptPathAttribute = typeof(ScriptPathAttribute); + + private static AssemblyLoadEventHandler? _assemblyLoadEventHandler; + + private static void InitializeIfNeeded() + { + if (_assemblyLoadEventHandler != null) return; + Initialize(); + System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())! + .Unloading += _ => { Deinitialize(); }; + } + + private static void Initialize() + { + GD.Print($"Initializing {nameof(GodotScriptPathCache)}"); + TypeToPath.Clear(); + PathToType.Clear(); + _assemblyLoadEventHandler = (sender, args) => CacheScriptsInAssembly(args.LoadedAssembly); + AppDomain.CurrentDomain.AssemblyLoad += _assemblyLoadEventHandler; + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + CacheScriptsInAssembly(assembly); + return; + + void CacheScriptsInAssembly(Assembly assembly) + { + foreach (var type in assembly.GetTypes() + .Where(t => t.IsClass) + .Where(t => t.IsSubclassOf(GodotObject))) + // get script path attribute + if (type.GetCustomAttributes(ScriptPathAttribute, false).FirstOrDefault() is ScriptPathAttribute + scriptPath) + { + TypeToPath[type] = scriptPath.Path; + PathToType[scriptPath.Path] = type; + } + } + } + + private static void Deinitialize() + { + GD.Print($"Deinitializing {nameof(GodotScriptPathCache)}"); + AppDomain.CurrentDomain.AssemblyLoad -= _assemblyLoadEventHandler; + _assemblyLoadEventHandler = null; + TypeToPath.Clear(); + PathToType.Clear(); + } + + public static bool TryGetScriptPath(Type type, [MaybeNullWhen(false)] out string path) + { + InitializeIfNeeded(); + return TypeToPath.TryGetValue(type, out path); + } + + public static Script GetScriptFromType(Type type) + { + InitializeIfNeeded(); + return TryGetScriptPath(type, out string path) + ? GD.Load