/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Godot;
using GodotDictionary = Godot.Collections.Dictionary;
public static class U
{
public static string NamePath( this Node current )
{
if( current is null )
return "";
var curName = current.Name.ToString();
return current.GetParent().NamePath() + "/" + curName;
}
///
/// Creates a containing the necessary keys for use with an object overriding
/// _GetPropertyList.
///
public static GodotDictionary MakeProperty(
Type classType,
string name,
Variant.Type type,
PropertyUsageFlags usage = PropertyUsageFlags.Default,
PropertyHint hint = PropertyHint.None,
string? hintString = "" )
{
return new GodotDictionary
{
["name"] = name,
["class_name"] = classType.Name,
["type"] = Variant.From( type ),
["usage"] = Variant.From( usage ),
["hint"] = Variant.From( hint ),
["hint_string"] = hintString ?? ""
};
}
///
/// Create a hint string for the given enum type to be used when setting the hint_string entry of a
/// _GetPropertyList result.
///
///
public static string EnumHintString()
where T : Enum
{
var s = string.Empty;
foreach( var e in Enum.GetValues( typeof( T ) ) )
{
if( !string.IsNullOrWhiteSpace( s ) )
s += ",";
s += $"{e}:{(int)e}";
}
return s;
}
public static string EnumHintString( Type type )
{
var s = string.Empty;
foreach( var e in Enum.GetValues( type ) )
{
if( !string.IsNullOrWhiteSpace( s ) )
s += ",";
s += $"{e}:{(int)e}";
}
return s;
}
private static readonly HashSet ActionTypes = new() {
typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>),
typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>)
};
private static readonly HashSet FunctionTypes = new() {
typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>),
typeof(Func<,,,,>), typeof(Func<,,,,,>), typeof(Func<,,,,,,>)
};
private static bool TypeIsAction( Type t )
=> ActionTypes.Contains( t ) || ( t.IsGenericType && ActionTypes.Contains( t.GetGenericTypeDefinition() ) );
private static bool TypeIsFunc( Type t )
=> FunctionTypes.Contains( t ) || ( t.IsGenericType && FunctionTypes.Contains( t.GetGenericTypeDefinition() ) );
///
/// Attempt to retrieve a function reference to the method with the given methodName in the given
/// targetNode.
///
///
/// The delegate for the method, or null if the editor is currently running. This is to avoid throwing
/// unnecessary exceptions when using a Tool class in the editor. This function will not return null
/// during application runtime.
///
///
/// The method is not found or has invalid parameters.
///
public static T? GetMethodDelegateForNode( Node targetNode, string methodName )
where T : Delegate
{
if( targetNode is null )
{
throw new ArgumentNullException( nameof( targetNode ) );
}
if( !TypeIsAction( typeof( T ) ) && !TypeIsFunc( typeof( T ) ) )
{
throw new InvalidOperationException( "The delegate being created must be of type Action or Func" );
}
var method = targetNode.GetType()
.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance )
.FirstOrDefault( m => m.Name == methodName );
if( method is null )
{
// Method with given name not found
//if (Engine.EditorHint) {
// If the editor is running, we do not any information on non-Tool types. We don't want to throw
// errors when there isn't actually a problem, so let's just let the caller deal with it.
// return null;
//}
throw new ArgumentException( $"Unable to find method \"{methodName}\" in node \"{targetNode.Name}\"" );
}
if( TypeIsAction( typeof( T ) ) )
{
var generics = typeof( T ).GetGenericArguments();
if( method.GetParameters().Count() != generics.Count()
|| method.GetParameters()
.Zip( generics, ( p, t ) => (p, t) )
.Any( pair => pair.p.ParameterType != pair.t ) )
{
// Arguments don't match
throw new ArgumentException( $"Invalid parameters for method \"{methodName}\"" );
}
}
else if( TypeIsFunc( typeof( T ) ) )
{
var generics = typeof( T ).GetGenericArguments();
if( generics.Any() )
{
var ret = generics[0];
var args = generics.Skip( 1 ).ToList();
if( method.ReturnType != ret )
{
throw new ArgumentException( $"Invalid return type for method \"{methodName}\"" );
}
if( method.GetParameters()
.Zip( args, ( p, t ) => (p, t) )
.Any( pair => pair.p.ParameterType != pair.t ) )
{
throw new ArgumentException( $"Invalid parameter types for method \"{methodName}\"" );
}
}
}
return (T)Delegate.CreateDelegate( typeof( T ), targetNode, methodName );
}
}
///
/// Dummy attribute to mark exports that are already handled by an overridden _GetPropertyList.
///
[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )]
public class ExportFakeAttribute : Attribute
{
public ExportFakeAttribute()
{ }
}
public static class NodeExtension
{
public static IEnumerable GetChildNodes( this Node node )
=> node.GetChildren().Cast();
}
// Needed to prevent some dumb ass error.
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}