C#: Add initial implementation of source generator for script members
This replaces the way we invoke methods and set/get properties. This first iteration rids us of runtime type checking in those cases, as it's now done at compile time. Later it will also stop needing the use of reflection. After that, we will only depend on reflection for generic Godot Array and Dictionary. We're stuck with reflection in generic collections for now as C# doesn't support generic/template specialization. This is only the initial implementation. Further iterations are coming, specially once we switch to the native extension system which completely changes the way members are accessed/invoked. For example, with the native extension system we will likely need to create `UnmanagedCallersOnly` invoke wrapper methods and return function pointers to the engine. Other kind of members, like event signals will be receiving the same treatment in the future.
This commit is contained in:
parent
e5e7a795b1
commit
4d710bf659
@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot>
|
||||
<PackageVersion_Godot_NET_Sdk>4.0.0-dev6</PackageVersion_Godot_NET_Sdk>
|
||||
<PackageVersion_Godot_SourceGenerators>4.0.0-dev3</PackageVersion_Godot_SourceGenerators>
|
||||
<PackageVersion_Godot_NET_Sdk>4.0.0-dev7</PackageVersion_Godot_NET_Sdk>
|
||||
<PackageVersion_Godot_SourceGenerators>4.0.0-dev6</PackageVersion_Godot_SourceGenerators>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@ -0,0 +1,21 @@
|
||||
namespace Godot.SourceGenerators.Sample
|
||||
{
|
||||
public partial class ScriptBoilerplate : Godot.Node
|
||||
{
|
||||
private NodePath _nodePath;
|
||||
private int _velocity;
|
||||
|
||||
public override void _Process(float delta)
|
||||
{
|
||||
_ = delta;
|
||||
|
||||
base._Process(delta);
|
||||
}
|
||||
|
||||
public int Bazz(StringName name)
|
||||
{
|
||||
_ = name;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -14,9 +14,8 @@ namespace Godot.SourceGenerators
|
||||
"Missing partial modifier on declaration of type '" +
|
||||
$"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'";
|
||||
|
||||
string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " +
|
||||
"declared with the partial modifier or annotated with the " +
|
||||
$"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'.";
|
||||
string description = $"{message}. Subclasses of '{GodotClasses.Object}' " +
|
||||
"must be declared with the partial modifier.";
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
new DiagnosticDescriptor(id: "GODOT-G0001",
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@ -13,6 +14,11 @@ namespace Godot.SourceGenerators
|
||||
) => context.AnalyzerConfigOptions.GlobalOptions
|
||||
.TryGetValue("build_property." + property, out value);
|
||||
|
||||
public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context)
|
||||
=> context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) &&
|
||||
toggle != null &&
|
||||
toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
|
||||
{
|
||||
if (symbol == null)
|
||||
@ -72,15 +78,11 @@ namespace Godot.SourceGenerators
|
||||
public static bool IsPartial(this ClassDeclarationSyntax cds)
|
||||
=> cds.Modifiers.Any(SyntaxKind.PartialKeyword);
|
||||
|
||||
public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol)
|
||||
=> symbol.GetAttributes().Any(attr =>
|
||||
attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr);
|
||||
|
||||
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
|
||||
|
||||
public static string FullQualifiedName(this INamedTypeSymbol symbol)
|
||||
public static string FullQualifiedName(this ITypeSymbol symbol)
|
||||
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
|
||||
|
||||
public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol)
|
||||
|
@ -2,6 +2,6 @@
|
||||
<ItemGroup>
|
||||
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
|
||||
<CompilerVisibleProperty Include="GodotProjectDir" />
|
||||
<CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
|
||||
<CompilerVisibleProperty Include="EnableGodotGenerators" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -3,7 +3,6 @@ namespace Godot.SourceGenerators
|
||||
public static class GodotClasses
|
||||
{
|
||||
public const string Object = "Godot.Object";
|
||||
public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute";
|
||||
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public enum MarshalType
|
||||
{
|
||||
Boolean,
|
||||
Char,
|
||||
SByte,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
Byte,
|
||||
UInt16,
|
||||
UInt32,
|
||||
UInt64,
|
||||
Single,
|
||||
Double,
|
||||
String,
|
||||
|
||||
// Godot structs
|
||||
Vector2,
|
||||
Vector2i,
|
||||
Rect2,
|
||||
Rect2i,
|
||||
Transform2D,
|
||||
Vector3,
|
||||
Vector3i,
|
||||
Basis,
|
||||
Quaternion,
|
||||
Transform3D,
|
||||
AABB,
|
||||
Color,
|
||||
Plane,
|
||||
Callable,
|
||||
SignalInfo,
|
||||
|
||||
// Enums
|
||||
Enum,
|
||||
|
||||
// Arrays
|
||||
ByteArray,
|
||||
Int32Array,
|
||||
Int64Array,
|
||||
SingleArray,
|
||||
DoubleArray,
|
||||
StringArray,
|
||||
Vector2Array,
|
||||
Vector3Array,
|
||||
ColorArray,
|
||||
GodotObjectOrDerivedArray,
|
||||
SystemObjectArray,
|
||||
|
||||
// Generics
|
||||
GodotGenericDictionary,
|
||||
GodotGenericArray,
|
||||
SystemGenericDictionary,
|
||||
SystemGenericList,
|
||||
GenericIDictionary,
|
||||
GenericICollection,
|
||||
GenericIEnumerable,
|
||||
|
||||
// Variant
|
||||
SystemObject,
|
||||
|
||||
// Classes
|
||||
GodotObjectOrDerived,
|
||||
StringName,
|
||||
NodePath,
|
||||
RID,
|
||||
GodotDictionary,
|
||||
GodotArray,
|
||||
IDictionary,
|
||||
ICollection,
|
||||
IEnumerable,
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
public static class MarshalUtils
|
||||
{
|
||||
public class TypeCache
|
||||
{
|
||||
public INamedTypeSymbol GodotObjectType { get; }
|
||||
public INamedTypeSymbol GodotGenericDictionary { get; }
|
||||
public INamedTypeSymbol GodotGenericArray { get; }
|
||||
public INamedTypeSymbol IDictionary { get; }
|
||||
public INamedTypeSymbol ICollection { get; }
|
||||
public INamedTypeSymbol GenericIDictionary { get; }
|
||||
public INamedTypeSymbol SystemGenericDictionary { get; }
|
||||
public INamedTypeSymbol SystemGenericList { get; }
|
||||
|
||||
public TypeCache(GeneratorExecutionContext context)
|
||||
{
|
||||
INamedTypeSymbol GetTypeByMetadataNameOrThrow(string fullyQualifiedMetadataName)
|
||||
{
|
||||
return context.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ??
|
||||
throw new InvalidOperationException("Type not found: " + fullyQualifiedMetadataName);
|
||||
}
|
||||
|
||||
GodotObjectType = GetTypeByMetadataNameOrThrow("Godot.Object");
|
||||
GodotGenericDictionary = GetTypeByMetadataNameOrThrow("Godot.Collections.Dictionary`2");
|
||||
GodotGenericArray = GetTypeByMetadataNameOrThrow("Godot.Collections.Array`1");
|
||||
IDictionary = GetTypeByMetadataNameOrThrow("System.Collections.IDictionary");
|
||||
ICollection = GetTypeByMetadataNameOrThrow("System.Collections.ICollection");
|
||||
GenericIDictionary = GetTypeByMetadataNameOrThrow("System.Collections.Generic.IDictionary`2");
|
||||
SystemGenericDictionary = GetTypeByMetadataNameOrThrow("System.Collections.Generic.Dictionary`2");
|
||||
SystemGenericList = GetTypeByMetadataNameOrThrow("System.Collections.Generic.List`1");
|
||||
}
|
||||
}
|
||||
|
||||
public static MarshalType? ConvertManagedTypeToVariantType(ITypeSymbol type, TypeCache typeCache)
|
||||
{
|
||||
var specialType = type.SpecialType;
|
||||
|
||||
switch (specialType)
|
||||
{
|
||||
case SpecialType.System_Boolean:
|
||||
return MarshalType.Boolean;
|
||||
case SpecialType.System_Char:
|
||||
return MarshalType.Char;
|
||||
case SpecialType.System_SByte:
|
||||
return MarshalType.SByte;
|
||||
case SpecialType.System_Int16:
|
||||
return MarshalType.Int16;
|
||||
case SpecialType.System_Int32:
|
||||
return MarshalType.Int32;
|
||||
case SpecialType.System_Int64:
|
||||
return MarshalType.Int64;
|
||||
case SpecialType.System_Byte:
|
||||
return MarshalType.Byte;
|
||||
case SpecialType.System_UInt16:
|
||||
return MarshalType.UInt16;
|
||||
case SpecialType.System_UInt32:
|
||||
return MarshalType.UInt32;
|
||||
case SpecialType.System_UInt64:
|
||||
return MarshalType.UInt64;
|
||||
case SpecialType.System_Single:
|
||||
return MarshalType.Single;
|
||||
case SpecialType.System_Double:
|
||||
return MarshalType.Double;
|
||||
case SpecialType.System_String:
|
||||
return MarshalType.String;
|
||||
case SpecialType.System_Object:
|
||||
return MarshalType.SystemObject;
|
||||
case SpecialType.System_ValueType:
|
||||
{
|
||||
if (type.ContainingAssembly.Name == "GodotSharp" &&
|
||||
type.ContainingNamespace.Name == "Godot")
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
{ Name: "Vector2" } => MarshalType.Vector2,
|
||||
{ Name: "Vector2i" } => MarshalType.Vector2i,
|
||||
{ Name: "Rect2" } => MarshalType.Rect2,
|
||||
{ Name: "Rect2i" } => MarshalType.Rect2i,
|
||||
{ Name: "Transform2D" } => MarshalType.Transform2D,
|
||||
{ Name: "Vector3" } => MarshalType.Vector3,
|
||||
{ Name: "Vector3i" } => MarshalType.Vector3i,
|
||||
{ Name: "Basis" } => MarshalType.Basis,
|
||||
{ Name: "Quaternion" } => MarshalType.Quaternion,
|
||||
{ Name: "Transform3D" } => MarshalType.Transform3D,
|
||||
{ Name: "AABB" } => MarshalType.AABB,
|
||||
{ Name: "Color" } => MarshalType.Color,
|
||||
{ Name: "Plane" } => MarshalType.Plane,
|
||||
{ Name: "RID" } => MarshalType.RID,
|
||||
{ Name: "Callable" } => MarshalType.Callable,
|
||||
{ Name: "SignalInfo" } => MarshalType.SignalInfo,
|
||||
{ TypeKind: TypeKind.Enum } => MarshalType.Enum,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (type.TypeKind == TypeKind.Array)
|
||||
{
|
||||
var arrayType = (IArrayTypeSymbol)type;
|
||||
var elementType = arrayType.ElementType;
|
||||
|
||||
switch (elementType.SpecialType)
|
||||
{
|
||||
case SpecialType.System_Byte:
|
||||
return MarshalType.ByteArray;
|
||||
case SpecialType.System_Int32:
|
||||
return MarshalType.Int32Array;
|
||||
case SpecialType.System_Int64:
|
||||
return MarshalType.Int64Array;
|
||||
case SpecialType.System_Single:
|
||||
return MarshalType.SingleArray;
|
||||
case SpecialType.System_Double:
|
||||
return MarshalType.DoubleArray;
|
||||
case SpecialType.System_String:
|
||||
return MarshalType.StringArray;
|
||||
case SpecialType.System_Object:
|
||||
return MarshalType.SystemObjectArray;
|
||||
}
|
||||
|
||||
if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType))
|
||||
return MarshalType.GodotObjectOrDerivedArray;
|
||||
|
||||
if (type.ContainingAssembly.Name == "GodotSharp" &&
|
||||
type.ContainingNamespace.Name == "Godot")
|
||||
{
|
||||
return elementType switch
|
||||
{
|
||||
{ Name: "Vector2" } => MarshalType.Vector2Array,
|
||||
{ Name: "Vector3" } => MarshalType.Vector3Array,
|
||||
{ Name: "Color" } => MarshalType.ColorArray,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (type is INamedTypeSymbol { IsGenericType: true } genericType)
|
||||
{
|
||||
var genericTypeDef = genericType.ConstructedFrom;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.GodotGenericDictionary))
|
||||
return MarshalType.GodotGenericDictionary;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.GodotGenericArray))
|
||||
return MarshalType.GodotGenericArray;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.SystemGenericDictionary))
|
||||
return MarshalType.SystemGenericDictionary;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.SystemGenericList))
|
||||
return MarshalType.SystemGenericList;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.GenericIDictionary))
|
||||
return MarshalType.GenericIDictionary;
|
||||
|
||||
return genericTypeDef.SpecialType switch
|
||||
{
|
||||
SpecialType.System_Collections_Generic_ICollection_T => MarshalType.GenericICollection,
|
||||
SpecialType.System_Collections_Generic_IEnumerable_T => MarshalType.GenericIEnumerable,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type.SimpleDerivesFrom(typeCache.GodotObjectType))
|
||||
return MarshalType.GodotObjectOrDerived;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(type, typeCache.IDictionary))
|
||||
return MarshalType.IDictionary;
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(type, typeCache.ICollection))
|
||||
return MarshalType.ICollection;
|
||||
|
||||
if (specialType == SpecialType.System_Collections_IEnumerable)
|
||||
return MarshalType.IEnumerable;
|
||||
|
||||
if (type.ContainingAssembly.Name == "GodotSharp")
|
||||
{
|
||||
switch (type.ContainingNamespace.Name)
|
||||
{
|
||||
case "Godot":
|
||||
return type switch
|
||||
{
|
||||
{ Name: "StringName" } => MarshalType.StringName,
|
||||
{ Name: "NodePath" } => MarshalType.NodePath,
|
||||
_ => null
|
||||
};
|
||||
case "Godot.Collections" when !(type is INamedTypeSymbol { IsGenericType: true }):
|
||||
return type switch
|
||||
{
|
||||
{ Name: "Dictionary" } => MarshalType.GodotDictionary,
|
||||
{ Name: "Array" } => MarshalType.GodotArray,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool SimpleDerivesFrom(this ITypeSymbol? type, ITypeSymbol candidateBaseType)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(type, candidateBaseType))
|
||||
return true;
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,423 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
[Generator]
|
||||
public class ScriptBoilerplateGenerator : ISourceGenerator
|
||||
{
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
if (context.AreGodotSourceGeneratorsDisabled())
|
||||
return;
|
||||
|
||||
// False positive for RS1024. We're already using `SymbolEqualityComparer.Default`...
|
||||
#pragma warning disable RS1024
|
||||
INamedTypeSymbol[] godotClasses = context
|
||||
.Compilation.SyntaxTrees
|
||||
.SelectMany(tree =>
|
||||
tree.GetRoot().DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.SelectGodotScriptClasses(context.Compilation)
|
||||
// Report and skip non-partial classes
|
||||
.Where(x =>
|
||||
{
|
||||
if (x.cds.IsPartial())
|
||||
return true;
|
||||
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
|
||||
return false;
|
||||
})
|
||||
.Select(x => x.symbol)
|
||||
)
|
||||
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
|
||||
.ToArray();
|
||||
#pragma warning restore RS1024
|
||||
|
||||
if (godotClasses.Length > 0)
|
||||
{
|
||||
var typeCache = new MarshalUtils.TypeCache(context);
|
||||
|
||||
foreach (var godotClass in godotClasses)
|
||||
{
|
||||
VisitGodotScriptClass(context, typeCache, godotClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitGodotScriptClass(
|
||||
GeneratorExecutionContext context,
|
||||
MarshalUtils.TypeCache typeCache,
|
||||
INamedTypeSymbol symbol
|
||||
)
|
||||
{
|
||||
string className = symbol.Name;
|
||||
|
||||
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
|
||||
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
|
||||
namespaceSymbol.FullQualifiedName() :
|
||||
string.Empty;
|
||||
bool hasNamespace = classNs.Length != 0;
|
||||
|
||||
string uniqueName = hasNamespace ?
|
||||
classNs + "." + className + "_ScriptBoilerplate_Generated" :
|
||||
className + "_ScriptBoilerplate_Generated";
|
||||
|
||||
var source = new StringBuilder();
|
||||
|
||||
source.Append("using Godot;\n");
|
||||
source.Append("using Godot.NativeInterop;\n");
|
||||
source.Append("\n");
|
||||
|
||||
if (hasNamespace)
|
||||
{
|
||||
source.Append("namespace ");
|
||||
source.Append(classNs);
|
||||
source.Append(" {\n\n");
|
||||
}
|
||||
|
||||
source.Append("partial class ");
|
||||
source.Append(className);
|
||||
source.Append("\n{\n");
|
||||
|
||||
var members = symbol.GetMembers();
|
||||
|
||||
// TODO: Static static marshaling (no reflection, no runtime type checks)
|
||||
|
||||
var methodSymbols = members
|
||||
.Where(s => s.Kind == SymbolKind.Method)
|
||||
.Cast<IMethodSymbol>()
|
||||
.Where(m => m.MethodKind == MethodKind.Ordinary && !m.IsImplicitlyDeclared);
|
||||
|
||||
var propertySymbols = members
|
||||
.Where(s => s.Kind == SymbolKind.Property)
|
||||
.Cast<IPropertySymbol>();
|
||||
|
||||
var fieldSymbols = members
|
||||
.Where(s => s.Kind == SymbolKind.Field)
|
||||
.Cast<IFieldSymbol>()
|
||||
.Where(p => !p.IsImplicitlyDeclared);
|
||||
|
||||
var methods = WhereHasCompatibleGodotType(methodSymbols, typeCache).ToArray();
|
||||
var properties = WhereIsCompatibleGodotType(propertySymbols, typeCache).ToArray();
|
||||
var fields = WhereIsCompatibleGodotType(fieldSymbols, typeCache).ToArray();
|
||||
|
||||
source.Append(" private class GodotInternal {\n");
|
||||
|
||||
// Generate cached StringNames for methods and properties, for fast lookup
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
string methodName = method.Method.Name;
|
||||
source.Append(" public static readonly StringName MethodName_");
|
||||
source.Append(methodName);
|
||||
source.Append(" = \"");
|
||||
source.Append(methodName);
|
||||
source.Append("\";\n");
|
||||
}
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
string propertyName = property.Property.Name;
|
||||
source.Append(" public static readonly StringName PropName_");
|
||||
source.Append(propertyName);
|
||||
source.Append(" = \"");
|
||||
source.Append(propertyName);
|
||||
source.Append("\";\n");
|
||||
}
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
string fieldName = field.Field.Name;
|
||||
source.Append(" public static readonly StringName PropName_");
|
||||
source.Append(fieldName);
|
||||
source.Append(" = \"");
|
||||
source.Append(fieldName);
|
||||
source.Append("\";\n");
|
||||
}
|
||||
|
||||
source.Append(" }\n");
|
||||
|
||||
if (methods.Length > 0)
|
||||
{
|
||||
source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
|
||||
source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n");
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
GenerateMethodInvoker(method, source);
|
||||
}
|
||||
|
||||
source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
|
||||
|
||||
source.Append(" }\n");
|
||||
}
|
||||
|
||||
if (properties.Length > 0 || fields.Length > 0)
|
||||
{
|
||||
// Setters
|
||||
|
||||
source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
|
||||
source.Append("in godot_variant value)\n {\n");
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
GeneratePropertySetter(property.Property.Name,
|
||||
property.Property.Type.FullQualifiedName(), source);
|
||||
}
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
GeneratePropertySetter(field.Field.Name,
|
||||
field.Field.Type.FullQualifiedName(), source);
|
||||
}
|
||||
|
||||
source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
|
||||
|
||||
source.Append(" }\n");
|
||||
|
||||
// Getters
|
||||
|
||||
source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
|
||||
source.Append("out godot_variant value)\n {\n");
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
GeneratePropertyGetter(property.Property.Name, source);
|
||||
}
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
GeneratePropertyGetter(field.Field.Name, source);
|
||||
}
|
||||
|
||||
source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
|
||||
|
||||
source.Append(" }\n");
|
||||
}
|
||||
|
||||
source.Append("}\n");
|
||||
|
||||
if (hasNamespace)
|
||||
{
|
||||
source.Append("\n}\n");
|
||||
}
|
||||
|
||||
context.AddSource(uniqueName, SourceText.From(source.ToString(), Encoding.UTF8));
|
||||
}
|
||||
|
||||
private static void GenerateMethodInvoker(
|
||||
GodotMethodInfo method,
|
||||
StringBuilder source
|
||||
)
|
||||
{
|
||||
string methodName = method.Method.Name;
|
||||
|
||||
source.Append(" if (method == GodotInternal.MethodName_");
|
||||
source.Append(methodName);
|
||||
source.Append(" && argCount == ");
|
||||
source.Append(method.ParamTypes.Length);
|
||||
source.Append(") {\n");
|
||||
|
||||
if (method.RetType != null)
|
||||
source.Append(" object retBoxed = ");
|
||||
else
|
||||
source.Append(" ");
|
||||
|
||||
source.Append(methodName);
|
||||
source.Append("(");
|
||||
|
||||
for (int i = 0; i < method.ParamTypes.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
source.Append(", ");
|
||||
|
||||
// TODO: static marshaling (no reflection, no runtime type checks)
|
||||
|
||||
string paramTypeQualifiedName = method.ParamTypeSymbols[i].FullQualifiedName();
|
||||
|
||||
source.Append("(");
|
||||
source.Append(paramTypeQualifiedName);
|
||||
source.Append(")Marshaling.ConvertVariantToManagedObjectOfType(args[");
|
||||
source.Append(i);
|
||||
source.Append("], typeof(");
|
||||
source.Append(paramTypeQualifiedName);
|
||||
source.Append("))");
|
||||
}
|
||||
|
||||
source.Append(");\n");
|
||||
|
||||
if (method.RetType != null)
|
||||
{
|
||||
// TODO: static marshaling (no reflection, no runtime type checks)
|
||||
source.Append(" ret = Marshaling.ConvertManagedObjectToVariant(retBoxed);\n");
|
||||
source.Append(" return true;\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
source.Append(" ret = default;\n");
|
||||
source.Append(" return true;\n");
|
||||
}
|
||||
|
||||
source.Append(" }\n");
|
||||
}
|
||||
|
||||
private static void GeneratePropertySetter(
|
||||
string propertyMemberName,
|
||||
string propertyTypeQualifiedName,
|
||||
StringBuilder source
|
||||
)
|
||||
{
|
||||
source.Append(" if (name == GodotInternal.PropName_");
|
||||
source.Append(propertyMemberName);
|
||||
source.Append(") {\n");
|
||||
|
||||
source.Append(" ");
|
||||
source.Append(propertyMemberName);
|
||||
source.Append(" = ");
|
||||
|
||||
// TODO: static marshaling (no reflection, no runtime type checks)
|
||||
|
||||
source.Append("(");
|
||||
source.Append(propertyTypeQualifiedName);
|
||||
source.Append(")Marshaling.ConvertVariantToManagedObjectOfType(value, typeof(");
|
||||
source.Append(propertyTypeQualifiedName);
|
||||
source.Append("));\n");
|
||||
|
||||
source.Append(" return true;\n");
|
||||
|
||||
source.Append(" }\n");
|
||||
}
|
||||
|
||||
private static void GeneratePropertyGetter(
|
||||
string propertyMemberName,
|
||||
StringBuilder source
|
||||
)
|
||||
{
|
||||
source.Append(" if (name == GodotInternal.PropName_");
|
||||
source.Append(propertyMemberName);
|
||||
source.Append(") {\n");
|
||||
|
||||
// TODO: static marshaling (no reflection, no runtime type checks)
|
||||
|
||||
source.Append(" value = Marshaling.ConvertManagedObjectToVariant(");
|
||||
source.Append(propertyMemberName);
|
||||
source.Append(");\n");
|
||||
source.Append(" return true;\n");
|
||||
|
||||
source.Append(" }\n");
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
}
|
||||
|
||||
private struct GodotMethodInfo
|
||||
{
|
||||
public GodotMethodInfo(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes,
|
||||
ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType)
|
||||
{
|
||||
Method = method;
|
||||
ParamTypes = paramTypes;
|
||||
ParamTypeSymbols = paramTypeSymbols;
|
||||
RetType = retType;
|
||||
}
|
||||
|
||||
public IMethodSymbol Method { get; }
|
||||
public ImmutableArray<MarshalType> ParamTypes { get; }
|
||||
public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
|
||||
public MarshalType? RetType { get; }
|
||||
}
|
||||
|
||||
private struct GodotPropertyInfo
|
||||
{
|
||||
public GodotPropertyInfo(IPropertySymbol property, MarshalType type)
|
||||
{
|
||||
Property = property;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public IPropertySymbol Property { get; }
|
||||
public MarshalType Type { get; }
|
||||
}
|
||||
|
||||
private struct GodotFieldInfo
|
||||
{
|
||||
public GodotFieldInfo(IFieldSymbol field, MarshalType type)
|
||||
{
|
||||
Field = field;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public IFieldSymbol Field { get; }
|
||||
public MarshalType Type { get; }
|
||||
}
|
||||
|
||||
private static IEnumerable<GodotMethodInfo> WhereHasCompatibleGodotType(
|
||||
IEnumerable<IMethodSymbol> methods,
|
||||
MarshalUtils.TypeCache typeCache
|
||||
)
|
||||
{
|
||||
foreach (var method in methods)
|
||||
{
|
||||
if (method.IsGenericMethod)
|
||||
continue;
|
||||
|
||||
var retType = method.ReturnsVoid ?
|
||||
null :
|
||||
MarshalUtils.ConvertManagedTypeToVariantType(method.ReturnType, typeCache);
|
||||
|
||||
if (retType == null && !method.ReturnsVoid)
|
||||
continue;
|
||||
|
||||
var parameters = method.Parameters;
|
||||
|
||||
var paramTypes = parameters.Select(p =>
|
||||
MarshalUtils.ConvertManagedTypeToVariantType(p.Type, typeCache))
|
||||
.Where(t => t != null).Cast<MarshalType>().ToImmutableArray();
|
||||
|
||||
if (parameters.Length > paramTypes.Length)
|
||||
continue; // Some param types weren't compatible
|
||||
|
||||
yield return new GodotMethodInfo(method, paramTypes, parameters
|
||||
.Select(p => p.Type).ToImmutableArray(), retType);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<GodotPropertyInfo> WhereIsCompatibleGodotType(
|
||||
IEnumerable<IPropertySymbol> properties,
|
||||
MarshalUtils.TypeCache typeCache
|
||||
)
|
||||
{
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(property.Type, typeCache);
|
||||
|
||||
if (marshalType == null)
|
||||
continue;
|
||||
|
||||
yield return new GodotPropertyInfo(property, marshalType.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<GodotFieldInfo> WhereIsCompatibleGodotType(
|
||||
IEnumerable<IFieldSymbol> fields,
|
||||
MarshalUtils.TypeCache typeCache
|
||||
)
|
||||
{
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(field.Type, typeCache);
|
||||
|
||||
if (marshalType == null)
|
||||
continue;
|
||||
|
||||
yield return new GodotFieldInfo(field, marshalType.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,13 +14,10 @@ namespace Godot.SourceGenerators
|
||||
{
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle)
|
||||
&& toggle == "disabled")
|
||||
{
|
||||
if (context.AreGodotSourceGeneratorsDisabled())
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: IsNullOrEmpty doesn't work well with nullable checks
|
||||
// NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0
|
||||
// ReSharper disable once ReplaceWithStringIsNullOrEmpty
|
||||
if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir)
|
||||
|| godotProjectDir!.Length == 0)
|
||||
@ -28,7 +25,8 @@ namespace Godot.SourceGenerators
|
||||
throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty.");
|
||||
}
|
||||
|
||||
var godotClasses = context.Compilation.SyntaxTrees
|
||||
Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context
|
||||
.Compilation.SyntaxTrees
|
||||
.SelectMany(tree =>
|
||||
tree.GetRoot().DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
@ -38,7 +36,7 @@ namespace Godot.SourceGenerators
|
||||
// Report and skip non-partial classes
|
||||
.Where(x =>
|
||||
{
|
||||
if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute())
|
||||
if (x.cds.IsPartial())
|
||||
return true;
|
||||
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
|
||||
return false;
|
||||
|
@ -0,0 +1,19 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
// Placeholder. Once we switch to native extensions this will act as the registrar for all
|
||||
// user Godot classes in the assembly. Think of it as something similar to `register_types`.
|
||||
public class ScriptRegistrarGenerator : ISourceGenerator
|
||||
{
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -81,9 +81,12 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
|
||||
#define CS_STATIC_METHOD_GETINSTANCE "GetPtr"
|
||||
#define CS_METHOD_CALL "Call"
|
||||
#define CS_PROPERTY_SINGLETON "Singleton"
|
||||
#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"
|
||||
|
||||
#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor"
|
||||
#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind"
|
||||
#define CS_STATIC_FIELD_METHOD_NAME_PREFIX "MethodName_"
|
||||
#define CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX "MethodProxyName_"
|
||||
#define CS_STATIC_FIELD_SIGNAL_NAME_PREFIX "SignalName_"
|
||||
|
||||
#define ICALL_PREFIX "godot_icall_"
|
||||
@ -1604,11 +1607,27 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
|
||||
// Script calls
|
||||
|
||||
if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) {
|
||||
// TODO: string is ok for now. But should be replaced with StringName in the future for performance.
|
||||
for (const MethodInterface &imethod : itype.methods) {
|
||||
if (!imethod.is_virtual) {
|
||||
continue;
|
||||
}
|
||||
|
||||
output << MEMBER_BEGIN "internal " << (is_derived_type ? "override" : "virtual")
|
||||
<< " bool InternalGodotScriptCall(string method, NativeVariantPtrArgs args, "
|
||||
<< "int argCount, out godot_variant ret)\n"
|
||||
output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"
|
||||
<< INDENT2 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
|
||||
<< INDENT2 "private static readonly StringName "
|
||||
<< CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name
|
||||
<< " = \"" << imethod.name << "\";\n";
|
||||
|
||||
output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"
|
||||
<< INDENT2 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
|
||||
<< INDENT2 "private static readonly StringName "
|
||||
<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
|
||||
<< " = \"" << imethod.proxy_name << "\";\n";
|
||||
}
|
||||
|
||||
output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
|
||||
<< " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "
|
||||
<< "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n"
|
||||
<< INDENT2 "{\n";
|
||||
|
||||
for (const MethodInterface &imethod : itype.methods) {
|
||||
@ -1616,11 +1635,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Compare with cached StringName. We already have a cached StringName
|
||||
// field for the proxy name. We need one for the original snake_case name.
|
||||
output << INDENT3 "if ((method == nameof(" << imethod.proxy_name << ") || method == \"" << imethod.name
|
||||
<< "\") && argCount == " << itos(imethod.arguments.size()) << ")\n"
|
||||
output << INDENT3 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
|
||||
<< " || method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name
|
||||
<< ") && argCount == " << itos(imethod.arguments.size()) << ")\n"
|
||||
<< INDENT3 "{\n";
|
||||
|
||||
if (imethod.return_type.cname != name_cache.type_void) {
|
||||
@ -1668,9 +1685,10 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
|
||||
}
|
||||
|
||||
if (is_derived_type) {
|
||||
output << INDENT3 "return base.InternalGodotScriptCall(method, args, argCount, out ret);\n";
|
||||
output << INDENT3 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n";
|
||||
} else {
|
||||
output << INDENT3 "return InternalGodotScriptCallViaReflection(method, args, argCount, out ret);\n";
|
||||
output << INDENT3 "ret = default;\n"
|
||||
<< INDENT3 "return false;\n";
|
||||
}
|
||||
|
||||
output << INDENT2 "}\n";
|
||||
@ -1989,8 +2007,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
|
||||
// Generate method
|
||||
{
|
||||
if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
|
||||
p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static readonly IntPtr "
|
||||
<< method_bind_field << " = ";
|
||||
p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
|
||||
<< INDENT2 "private static readonly IntPtr " << method_bind_field << " = ";
|
||||
|
||||
if (p_itype.is_singleton) {
|
||||
// Singletons are static classes. They don't derive Godot.Object,
|
||||
@ -2024,16 +2042,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
|
||||
p_output.append(default_args_doc.as_string());
|
||||
}
|
||||
|
||||
if (!p_imethod.is_internal) {
|
||||
// TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be
|
||||
// better to generate a table in the C++ glue instead. That way the strings wouldn't
|
||||
// add that much extra bloat as they're already used in engine code. Also, it would
|
||||
// probably be much faster than looking up the attributes when fetching methods.
|
||||
p_output.append(MEMBER_BEGIN "[GodotMethod(\"");
|
||||
p_output.append(p_imethod.name);
|
||||
p_output.append("\")]");
|
||||
}
|
||||
|
||||
if (p_imethod.is_deprecated) {
|
||||
if (p_imethod.deprecation_message.is_empty()) {
|
||||
WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'.");
|
||||
@ -2196,8 +2204,9 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
|
||||
// If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here.
|
||||
|
||||
// Cached signal name (StringName)
|
||||
p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN
|
||||
"private static readonly StringName " CS_STATIC_FIELD_SIGNAL_NAME_PREFIX);
|
||||
p_output.append(MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n");
|
||||
p_output.append(INDENT2 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN
|
||||
"private static readonly StringName " CS_STATIC_FIELD_SIGNAL_NAME_PREFIX);
|
||||
p_output.append(p_isignal.name);
|
||||
p_output.append(" = \"");
|
||||
p_output.append(p_isignal.name);
|
||||
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute that disables Godot Generators.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableGodotGeneratorsAttribute : Attribute { }
|
||||
}
|
@ -12,7 +12,6 @@ namespace Godot.Bridge
|
||||
{
|
||||
try
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
|
||||
|
||||
if (godotObject == null)
|
||||
@ -27,7 +26,7 @@ namespace Godot.Bridge
|
||||
using (dest)
|
||||
methodStr = Marshaling.ConvertStringToManaged(dest);
|
||||
|
||||
bool methodInvoked = godotObject.InternalGodotScriptCall(methodStr, new NativeVariantPtrArgs(args),
|
||||
bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args),
|
||||
argCount, out godot_variant retValue);
|
||||
|
||||
if (!methodInvoked)
|
||||
@ -55,21 +54,19 @@ namespace Godot.Bridge
|
||||
{
|
||||
try
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
|
||||
|
||||
if (godotObject == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
|
||||
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name)));
|
||||
|
||||
if (godotObject.InternalGodotScriptSetFieldOrPropViaReflection(
|
||||
nameManaged.ToString(), CustomUnsafe.AsRef(value)))
|
||||
if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value)))
|
||||
{
|
||||
return true.ToGodotBool();
|
||||
}
|
||||
|
||||
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
|
||||
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name)));
|
||||
|
||||
object valueManaged = Marshaling.ConvertVariantToManagedObject(CustomUnsafe.AsRef(value));
|
||||
|
||||
return godotObject._Set(nameManaged, valueManaged).ToGodotBool();
|
||||
@ -87,22 +84,20 @@ namespace Godot.Bridge
|
||||
{
|
||||
try
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
|
||||
|
||||
if (godotObject == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
|
||||
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name)));
|
||||
|
||||
if (godotObject.InternalGodotScriptGetFieldOrPropViaReflection(nameManaged.ToString(),
|
||||
out godot_variant outRetValue))
|
||||
if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue))
|
||||
{
|
||||
*outRet = outRetValue;
|
||||
return true.ToGodotBool();
|
||||
}
|
||||
|
||||
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
|
||||
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name)));
|
||||
|
||||
object ret = godotObject._Get(nameManaged);
|
||||
|
||||
if (ret == null)
|
||||
|
@ -487,6 +487,31 @@ namespace Godot.NativeInterop
|
||||
get => _data == IntPtr.Zero;
|
||||
}
|
||||
|
||||
public static bool operator ==(godot_string_name left, godot_string_name right)
|
||||
{
|
||||
return left._data == right._data;
|
||||
}
|
||||
|
||||
public static bool operator !=(godot_string_name left, godot_string_name right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public bool Equals(godot_string_name other)
|
||||
{
|
||||
return _data == other._data;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is StringName s && s.Equals(this);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _data.GetHashCode();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal struct movable
|
||||
|
@ -26,7 +26,7 @@ namespace Godot.NativeInterop
|
||||
|
||||
[DllImport(GodotDllName)]
|
||||
public static extern IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname,
|
||||
char* p_methodname);
|
||||
in godot_string_name p_methodname);
|
||||
|
||||
[DllImport(GodotDllName)]
|
||||
public static extern delegate* unmanaged<IntPtr> godotsharp_get_class_constructor(
|
||||
|
@ -185,125 +185,20 @@ namespace Godot
|
||||
return assemblyName.Name == "GodotSharp" || assemblyName.Name == "GodotSharpEditor";
|
||||
}
|
||||
|
||||
internal bool InternalGodotScriptCallViaReflection(string method, NativeVariantPtrArgs args, int argCount,
|
||||
out godot_variant ret)
|
||||
// ReSharper disable once VirtualMemberNeverOverridden.Global
|
||||
protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
Type top = GetType();
|
||||
Type native = InternalGetClassNativeBase(top);
|
||||
|
||||
while (top != null && top != native)
|
||||
{
|
||||
var methodInfo = top.GetMethod(method,
|
||||
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
||||
BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (methodInfo != null)
|
||||
{
|
||||
var parameters = methodInfo.GetParameters();
|
||||
int paramCount = parameters.Length;
|
||||
|
||||
if (argCount == paramCount)
|
||||
{
|
||||
object[] invokeParams = new object[paramCount];
|
||||
|
||||
for (int i = 0; i < paramCount; i++)
|
||||
{
|
||||
invokeParams[i] = Marshaling.ConvertVariantToManagedObjectOfType(
|
||||
args[i], parameters[i].ParameterType);
|
||||
}
|
||||
|
||||
object retObj = methodInfo.Invoke(this, invokeParams);
|
||||
|
||||
ret = Marshaling.ConvertManagedObjectToVariant(retObj);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
top = top.BaseType;
|
||||
}
|
||||
|
||||
ret = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool InternalGodotScriptSetFieldOrPropViaReflection(string name, in godot_variant value)
|
||||
// ReSharper disable once VirtualMemberNeverOverridden.Global
|
||||
protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
Type top = GetType();
|
||||
Type native = InternalGetClassNativeBase(top);
|
||||
|
||||
while (top != null && top != native)
|
||||
{
|
||||
var fieldInfo = top.GetField(name,
|
||||
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
||||
BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
object valueManaged = Marshaling.ConvertVariantToManagedObjectOfType(value, fieldInfo.FieldType);
|
||||
fieldInfo.SetValue(this, valueManaged);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var propertyInfo = top.GetProperty(name,
|
||||
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
||||
BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
object valueManaged =
|
||||
Marshaling.ConvertVariantToManagedObjectOfType(value, propertyInfo.PropertyType);
|
||||
propertyInfo.SetValue(this, valueManaged);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
top = top.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool InternalGodotScriptGetFieldOrPropViaReflection(string name, out godot_variant value)
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
Type top = GetType();
|
||||
Type native = InternalGetClassNativeBase(top);
|
||||
|
||||
while (top != null && top != native)
|
||||
{
|
||||
var fieldInfo = top.GetField(name,
|
||||
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
||||
BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
object valueManaged = fieldInfo.GetValue(this);
|
||||
value = Marshaling.ConvertManagedObjectToVariant(valueManaged);
|
||||
return true;
|
||||
}
|
||||
|
||||
var propertyInfo = top.GetProperty(name,
|
||||
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
||||
BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
object valueManaged = propertyInfo.GetValue(this);
|
||||
value = Marshaling.ConvertManagedObjectToVariant(valueManaged);
|
||||
return true;
|
||||
}
|
||||
|
||||
top = top.BaseType;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal unsafe void InternalRaiseEventSignal(in godot_string_name eventSignalName, NativeVariantPtrArgs args,
|
||||
internal void InternalRaiseEventSignal(in godot_string_name eventSignalName, NativeVariantPtrArgs args,
|
||||
int argc)
|
||||
{
|
||||
// Performance is not critical here as this will be replaced with source generators.
|
||||
@ -371,14 +266,11 @@ namespace Godot
|
||||
}
|
||||
}
|
||||
|
||||
internal static unsafe IntPtr ClassDB_get_method(StringName type, string method)
|
||||
internal static IntPtr ClassDB_get_method(StringName type, StringName method)
|
||||
{
|
||||
IntPtr methodBind;
|
||||
fixed (char* methodChars = method)
|
||||
{
|
||||
var typeSelf = (godot_string_name)type.NativeValue;
|
||||
methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodChars);
|
||||
}
|
||||
var typeSelf = (godot_string_name)type.NativeValue;
|
||||
var methodSelf = (godot_string_name)method.NativeValue;
|
||||
IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodSelf);
|
||||
|
||||
if (methodBind == IntPtr.Zero)
|
||||
throw new NativeMethodBindNotFoundException(type + "." + method);
|
||||
|
@ -92,5 +92,51 @@ namespace Godot
|
||||
/// </summary>
|
||||
/// <returns>If the <see cref="StringName"/> is empty.</returns>
|
||||
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
|
||||
|
||||
public static bool operator ==(StringName left, StringName right)
|
||||
{
|
||||
if (left is null)
|
||||
return right is null;
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(StringName left, StringName right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public bool Equals(StringName other)
|
||||
{
|
||||
if (other is null)
|
||||
return false;
|
||||
return NativeValue.DangerousSelfRef == other.NativeValue.DangerousSelfRef;
|
||||
}
|
||||
|
||||
public static bool operator ==(StringName left, in godot_string_name right)
|
||||
{
|
||||
if (left is null)
|
||||
return right.IsEmpty;
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(StringName left, in godot_string_name right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public static bool operator ==(in godot_string_name left, StringName right)
|
||||
{
|
||||
return right == left;
|
||||
}
|
||||
|
||||
public static bool operator !=(in godot_string_name left, StringName right)
|
||||
{
|
||||
return !(right == left);
|
||||
}
|
||||
|
||||
public bool Equals(in godot_string_name other)
|
||||
{
|
||||
return NativeValue.DangerousSelfRef == other;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,6 @@
|
||||
<Compile Include="Core\AABB.cs" />
|
||||
<Compile Include="Core\Array.cs" />
|
||||
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\ExportAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\RPCAttribute.cs" />
|
||||
|
@ -65,8 +65,8 @@ static_assert(sizeof(SafeRefCount) == sizeof(uint32_t));
|
||||
|
||||
typedef Object *(*godotsharp_class_creation_func)();
|
||||
|
||||
GD_PINVOKE_EXPORT MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, const char16_t *p_methodname) {
|
||||
return ClassDB::get_method(*p_classname, StringName(String::utf16(p_methodname)));
|
||||
GD_PINVOKE_EXPORT MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, const StringName *p_methodname) {
|
||||
return ClassDB::get_method(*p_classname, *p_methodname);
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) {
|
||||
|
Loading…
Reference in New Issue
Block a user