Skip to content

Commit

Permalink
Merge pull request #13 from theowiik/add-autowiring
Browse files Browse the repository at this point in the history
Add autowiring & instancing
  • Loading branch information
theowiik authored Sep 16, 2023
2 parents 30d7629 + 11ecafd commit 78d5dbb
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 11 deletions.
18 changes: 9 additions & 9 deletions GodotSharper.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GodotSharp" Version="4.0.0" />
<PackageReference Include="Godot.SourceGenerators" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="GodotSharp" Version="4.0.0"/>
<PackageReference Include="Godot.SourceGenerators" Version="4.0.0"/>
</ItemGroup>
</Project>
5 changes: 5 additions & 0 deletions GodotSharper.csproj.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xml:space="preserve">
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=godotsharper/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
77 changes: 77 additions & 0 deletions GodotSharper/AutoGetNode/AutoGetNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Reflection;
using Godot;

namespace GodotSharper.AutoGetNode;

/// <summary>
/// A static class that provides extension methods to automatically get nodes in Godot game engine.
/// </summary>
public static class AutoGetNode
{
/// <summary>
/// Gets all the nodes in the specified node and wires them to the corresponding fields and properties.
/// </summary>
/// <param name="node">The node to get the nodes from.</param>
public static void GetNodes(this Node node)
{
WireMembers(node, GetFields(node));
WireMembers(node, GetProperties(node));
}

/// <summary>
/// Gets all the fields in the specified node.
/// </summary>
/// <param name="node">The node to get the fields from.</param>
/// <returns>An IEnumerable of FieldInfo objects.</returns>
private static IEnumerable<FieldInfo> GetFields(Node node)
{
return GetMembers<FieldInfo>(node);
}

/// <summary>
/// Gets all the properties in the specified node.
/// </summary>
/// <param name="node">The node to get the properties from.</param>
/// <returns>An IEnumerable of PropertyInfo objects.</returns>
private static IEnumerable<PropertyInfo> GetProperties(Node node)
{
return GetMembers<PropertyInfo>(node);
}

/// <summary>
/// Gets all the members of the specified type in the specified node.
/// </summary>
/// <typeparam name="T">The type of member to get.</typeparam>
/// <param name="node">The node to get the members from.</param>
/// <returns>An IEnumerable of T objects.</returns>
private static IEnumerable<T> GetMembers<T>(Node node)
where T : MemberInfo
{
if (node == null)
return new List<T>();

var members = node.GetType()
.GetMembers(
BindingFlags.FlattenHierarchy
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance
)
.OfType<T>();

return new List<T>(members);
}

/// <summary>
/// Wires all the members in the specified node to the corresponding nodes.
/// </summary>
/// <typeparam name="T">The type of member to wire.</typeparam>
/// <param name="node">The node to wire the members to.</param>
/// <param name="members">The members to wire.</param>
private static void WireMembers<T>(Node node, IEnumerable<T> members)
where T : MemberInfo
{
foreach (var member in members)
member.GetCustomAttribute<GetNodeAttribute>()?.SetNode(member, node);
}
}
63 changes: 63 additions & 0 deletions GodotSharper/AutoGetNode/GetNodeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Reflection;
using Godot;
using GodotSharper.Exceptions;

namespace GodotSharper.AutoGetNode;

/// <summary>
/// Attribute used to automatically get a node from the scene tree.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class GetNodeAttribute : Attribute
{
private readonly string _path;

private GetNodeAttribute(string nodePath)
{
_path = nodePath;
}

/// <summary>
/// Sets the node specified by the attribute on the given member of the provided node.
/// </summary>
/// <param name="memberInfo">The member to set the node on.</param>
/// <param name="node">The node to get the child node from.</param>
/// <exception cref="NodeNotFoundException">Thrown if the child node cannot be found.</exception>
/// <exception cref="ArgumentException">Thrown if the child node is not of the expected type.</exception>
public void SetNode(MemberInfo memberInfo, Node node)
{
var childNode = node.GetNodeOrNull(_path);

if (childNode == null)
{
node.GetTree().Quit();
throw new NodeNotFoundException($"Cannot find Node for NodePath '{_path}'");
}

var expectedType = memberInfo is FieldInfo fieldInfo
? fieldInfo.FieldType
: ((PropertyInfo)memberInfo).PropertyType;

if (childNode.GetType() != expectedType && !childNode.GetType().IsSubclassOf(expectedType))
{
node.GetTree().Quit();
throw new ArgumentException(
$"Node is not a valid type. Expected {expectedType} got {childNode.GetType()}"
);
}

switch (memberInfo)
{
case FieldInfo fieldInformation:
fieldInformation.SetValue(node, childNode);
break;
case PropertyInfo propertyInformation:
propertyInformation.SetValue(node, childNode);
break;
default:
throw new ArgumentException(
$"MemberInfo is not a valid type. Expected {nameof(FieldInfo)} or {nameof(PropertyInfo)} got {memberInfo.GetType()}"
);
}
}
}
11 changes: 11 additions & 0 deletions GodotSharper/Exceptions/NodeNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <summary>
/// Represents an exception that is thrown when a node is not found in the scene tree.
/// </summary>

namespace GodotSharper.Exceptions;

public sealed class NodeNotFoundException : Exception
{
public NodeNotFoundException(string message)
: base(message) { }
}
25 changes: 23 additions & 2 deletions GodotSharper/GDX.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
using Godot;

namespace GoSharper;
namespace GodotSharper;

/// <summary>
/// Extended "GD" class providing utility methods for working with Godot Engine.
/// </summary>
public static class GDX
{
public static void Printt(string message) => GD.Print(message);
/// <summary>
/// Loads a resource from the specified path and returns it as a strongly-typed object.
/// </summary>
/// <typeparam name="T">The type of the resource to load.</typeparam>
/// <param name="path">The path to the resource to load.</param>
/// <returns>The loaded resource as a strongly-typed object.</returns>
/// <exception cref="FileNotFoundException">Thrown if the resource could not be loaded.</exception>
public static T LoadOrFail<T>(string path)
where T : class
{
var node = GD.Load<T>(path);

if (node != null)
return node;

var msg = $"Could not load resource at {path}";
GD.PrintErr(msg);
throw new FileNotFoundException(msg);
}
}
46 changes: 46 additions & 0 deletions GodotSharper/Instancing/Instanter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Godot;

namespace GodotSharper.Instancing;

/// <summary>
/// A static class that provides a method for instantiating Godot nodes.
/// </summary>
public static class Instanter
{
private static readonly IDictionary<Type, string> s_typePathLookup =
new Dictionary<Type, string>();

/// <summary>
/// Instantiates a Godot node of the specified type.
/// </summary>
/// <typeparam name="T">The type of the node to instantiate.</typeparam>
/// <returns>The instantiated node.</returns>
/// <exception cref="FileNotFoundException">Thrown if the PackedSceneAttribute for the specified type is not found.</exception>
public static T Instantiate<T>()
where T : Node
{
var type = typeof(T);
string path;

if (s_typePathLookup.TryGetValue(type, out var value))
{
path = value;
}
else
{
var attr = (InstantiableAttribute)
Attribute.GetCustomAttribute(type, typeof(InstantiableAttribute));

if (attr == null)
throw new FileNotFoundException(
"Could not find a PackedSceneAttribute for " + type
);

path = attr.Path;
s_typePathLookup[type] = path;
}

var scene = GDX.LoadOrFail<PackedScene>(path);
return scene.Instantiate<T>();
}
}
22 changes: 22 additions & 0 deletions GodotSharper/Instancing/InstantiableAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace GodotSharper.Instancing;

/// <summary>
/// Attribute used to mark a class as instantiable in Godot.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class InstantiableAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="InstantiableAttribute" /> class with the specified path.
/// </summary>
/// <param name="path">The path to the scene file for the instantiable class.</param>
public InstantiableAttribute(string path)
{
Path = path;
}

/// <summary>
/// Gets the path to the scene file for the instantiable class.
/// </summary>
public string Path { get; }
}

0 comments on commit 78d5dbb

Please sign in to comment.