C#: Add source generator for method list

This commit is contained in:
Ignacio Roldán Etcheverry
2022-07-28 17:41:48 +02:00
parent 97713ff77a
commit a9892f2571
10 changed files with 514 additions and 294 deletions

View File

@ -1618,29 +1618,18 @@ bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_re
} }
void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const {
#warning TODO if (!script->is_valid() || !script->valid) {
#if 0
if (!script->is_valid() || !script->script_class) {
return; return;
} }
GD_MONO_SCOPE_THREAD_ATTACH; const CSharpScript *top = script.ptr();
while (top != nullptr) {
// TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. for (const CSharpScript::CSharpMethodInfo &E : top->methods) {
GDMonoClass *top = script->script_class; p_list->push_back(E.method_info);
while (top && top != script->native) {
const Vector<GDMonoMethod *> &methods = top->get_all_methods();
for (int i = 0; i < methods.size(); ++i) {
MethodInfo minfo = methods[i]->get_method_info();
if (minfo.name != CACHED_STRING_NAME(dotctor)) {
p_list->push_back(minfo);
}
} }
top = top->get_parent_class(); top = top->base_script.ptr();
} }
#endif
} }
bool CSharpInstance::has_method(const StringName &p_method) const { bool CSharpInstance::has_method(const StringName &p_method) const {
@ -2183,25 +2172,52 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool tool = false; bool tool = false;
// TODO: Use GDNative godot_dictionary
Array methods_array;
methods_array.~Array();
Dictionary rpc_functions_dict; Dictionary rpc_functions_dict;
// Destructor won't be called from C#, and I don't want to include the GDNative header
// only for this, so need to call the destructor manually before passing this to C#.
rpc_functions_dict.~Dictionary(); rpc_functions_dict.~Dictionary();
Dictionary signals_dict; Dictionary signals_dict;
// Destructor won't be called from C#, and I don't want to include the GDNative header
// only for this, so need to call the destructor manually before passing this to C#.
signals_dict.~Dictionary(); signals_dict.~Dictionary();
Ref<CSharpScript> base_script; Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
p_script.ptr(), &tool, &rpc_functions_dict, &signals_dict, &base_script); p_script.ptr(), &tool, &methods_array, &rpc_functions_dict, &signals_dict, &base_script);
p_script->tool = tool; p_script->tool = tool;
p_script->rpc_config.clear(); p_script->rpc_config.clear();
p_script->rpc_config = rpc_functions_dict; p_script->rpc_config = rpc_functions_dict;
// Methods
p_script->methods.clear();
p_script->methods.resize(methods_array.size());
int push_index = 0;
for (int i = 0; i < methods_array.size(); i++) {
Dictionary method_info_dict = methods_array[i];
StringName name = method_info_dict["name"];
MethodInfo mi;
mi.name = name;
Array params = method_info_dict["params"];
for (int j = 0; j < params.size(); j++) {
Dictionary param = params[j];
Variant::Type param_type = (Variant::Type)(int)param["type"];
PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
arg_info.usage = (uint32_t)param["usage"];
mi.arguments.push_back(arg_info);
}
p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi });
}
// Event signals // Event signals
// Performance is not critical here as this will be replaced with source generators. // Performance is not critical here as this will be replaced with source generators.
@ -2210,7 +2226,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
// Sigh... can't we just have capacity? // Sigh... can't we just have capacity?
p_script->event_signals.resize(signals_dict.size()); p_script->event_signals.resize(signals_dict.size());
int push_index = 0; push_index = 0;
for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) { for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) {
StringName name = *s; StringName name = *s;
@ -2407,28 +2423,27 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
return; return;
} }
#warning TODO const CSharpScript *top = this;
#if 0 while (top != nullptr) {
// TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. for (const CSharpMethodInfo &E : top->methods) {
GDMonoClass *top = script_class; p_list->push_back(E.method_info);
while (top && top != native) {
const Vector<GDMonoMethod *> &methods = top->get_all_methods();
for (int i = 0; i < methods.size(); ++i) {
MethodInfo minfo = methods[i]->get_method_info();
if (minfo.name != CACHED_STRING_NAME(dotctor)) {
p_list->push_back(methods[i]->get_method_info());
}
} }
top = top->get_parent_class(); top = top->base_script.ptr();
} }
#endif
} }
bool CSharpScript::has_method(const StringName &p_method) const { bool CSharpScript::has_method(const StringName &p_method) const {
// The equivalent of this will be implemented once we switch to the GDExtension system if (!valid) {
ERR_PRINT_ONCE("CSharpScript::has_method is not implemented"); return false;
}
for (const CSharpMethodInfo &E : methods) {
if (E.name == p_method) {
return true;
}
}
return false; return false;
} }
@ -2437,19 +2452,11 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
return MethodInfo(); return MethodInfo();
} }
#warning TODO for (const CSharpMethodInfo &E : methods) {
#if 0 if (E.name == p_method) {
GDMonoClass *top = script_class; return E.method_info;
while (top && top != native) {
GDMonoMethod *params = top->get_method_unknown_params(p_method);
if (params) {
return params->get_method_info();
} }
top = top->get_parent_class();
} }
#endif
return MethodInfo(); return MethodInfo();
} }

View File

@ -103,7 +103,13 @@ class CSharpScript : public Script {
MethodInfo method_info; MethodInfo method_info;
}; };
struct CSharpMethodInfo {
StringName name; // MethodInfo stores a string...
MethodInfo method_info;
};
Vector<EventSignalInfo> event_signals; Vector<EventSignalInfo> event_signals;
Vector<CSharpMethodInfo> methods;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
List<PropertyInfo> exported_members_cache; // members_cache List<PropertyInfo> exported_members_cache; // members_cache

View File

@ -0,0 +1,31 @@
using System.Diagnostics.CodeAnalysis;
namespace Godot.SourceGenerators.Sample;
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
public partial class Methods : Godot.Object
{
private void MethodWithOverload()
{
}
private void MethodWithOverload(int a)
{
}
private void MethodWithOverload(int a, int b)
{
}
// Should be ignored. The previous one is picked.
// ReSharper disable once UnusedMember.Local
private void MethodWithOverload(float a, float b)
{
}
// Generic methods should be ignored.
// ReSharper disable once UnusedMember.Local
private void GenericMethod<T>(T t)
{
}
}

View File

@ -400,6 +400,12 @@ namespace Godot.SourceGenerators
source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"), source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"),
MarshalType.Transform3D => MarshalType.Transform3D =>
source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"), source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"),
MarshalType.Vector4 =>
source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"),
MarshalType.Vector4i =>
source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"),
MarshalType.Projection =>
source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"),
MarshalType.AABB => MarshalType.AABB =>
source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"), source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"),
MarshalType.Color => MarshalType.Color =>
@ -535,6 +541,12 @@ namespace Godot.SourceGenerators
source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"), source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"),
MarshalType.Transform3D => MarshalType.Transform3D =>
source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"), source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"),
MarshalType.Vector4 =>
source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"),
MarshalType.Vector4i =>
source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"),
MarshalType.Projection =>
source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"),
MarshalType.AABB => MarshalType.AABB =>
source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"), source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"),
MarshalType.Color => MarshalType.Color =>

View File

@ -8,8 +8,12 @@ using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators namespace Godot.SourceGenerators
{ {
[Generator] [Generator]
public class ScriptMemberInvokerGenerator : ISourceGenerator public class ScriptMethodsGenerator : ISourceGenerator
{ {
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context) public void Execute(GeneratorExecutionContext context)
{ {
if (context.AreGodotSourceGeneratorsDisabled()) if (context.AreGodotSourceGeneratorsDisabled())
@ -54,6 +58,20 @@ namespace Godot.SourceGenerators
} }
} }
private class MethodOverloadEqualityComparer : IEqualityComparer<GodotMethodData>
{
public bool Equals(GodotMethodData x, GodotMethodData y)
=> x.ParamTypes.Length == y.ParamTypes.Length && x.Method.Name == y.Method.Name;
public int GetHashCode(GodotMethodData obj)
{
unchecked
{
return (obj.ParamTypes.Length.GetHashCode() * 397) ^ obj.Method.Name.GetHashCode();
}
}
}
private static void VisitGodotScriptClass( private static void VisitGodotScriptClass(
GeneratorExecutionContext context, GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache, MarshalUtils.TypeCache typeCache,
@ -69,7 +87,7 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null; bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptMemberInvoker_Generated"; + "_ScriptMethods_Generated";
var source = new StringBuilder(); var source = new StringBuilder();
@ -111,54 +129,21 @@ namespace Godot.SourceGenerators
.Cast<IMethodSymbol>() .Cast<IMethodSymbol>()
.Where(m => m.MethodKind == MethodKind.Ordinary); .Where(m => m.MethodKind == MethodKind.Ordinary);
var propertySymbols = members var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache)
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) .Distinct(new MethodOverloadEqualityComparer())
.Cast<IPropertySymbol>(); .ToArray();
var fieldSymbols = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
.Cast<IFieldSymbol>();
var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache).ToArray();
var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var signalDelegateSymbols = members
.Where(s => s.Kind == SymbolKind.NamedType)
.Cast<INamedTypeSymbol>()
.Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
.Where(s => s.GetAttributes()
.Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
List<GodotSignalDelegateData> godotSignalDelegates = new();
foreach (var signalDelegateSymbol in signalDelegateSymbols)
{
if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
continue;
string signalName = signalDelegateSymbol.Name;
signalName = signalName.Substring(0,
signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
var invokeMethodData = signalDelegateSymbol
.DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
if (invokeMethodData == null)
continue;
godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
}
source.Append(" private partial class GodotInternal {\n"); source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup // Generate cached StringNames for methods and properties, for fast lookup
// TODO: Move the generation of these cached StringNames to its own generator var distinctMethodNames = godotClassMethods
.Select(m => m.Method.Name)
.Distinct()
.ToArray();
foreach (var method in godotClassMethods) foreach (string methodName in distinctMethodNames)
{ {
string methodName = method.Method.Name;
source.Append(" public static readonly StringName MethodName_"); source.Append(" public static readonly StringName MethodName_");
source.Append(methodName); source.Append(methodName);
source.Append(" = \""); source.Append(" = \"");
@ -168,6 +153,36 @@ namespace Godot.SourceGenerators
source.Append(" }\n"); // class GodotInternal source.Append(" }\n"); // class GodotInternal
// Generate GetGodotMethodList
if (godotClassMethods.Length > 0)
{
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
source.Append(" internal new static ")
.Append(listType)
.Append(" GetGodotMethodList()\n {\n");
source.Append(" var methods = new ")
.Append(listType)
.Append("(")
.Append(godotClassMethods.Length)
.Append(");\n");
foreach (var method in godotClassMethods)
{
var methodInfo = DetermineMethodInfo(method);
AppendMethodInfo(source, methodInfo);
}
source.Append(" return methods;\n");
source.Append(" }\n");
source.Append("#pragma warning restore CS0109\n");
}
// Generate InvokeGodotClassMethod // Generate InvokeGodotClassMethod
if (godotClassMethods.Length > 0) if (godotClassMethods.Length > 0)
@ -187,14 +202,14 @@ namespace Godot.SourceGenerators
// Generate HasGodotClassMethod // Generate HasGodotClassMethod
if (godotClassMethods.Length > 0) if (distinctMethodNames.Length > 0)
{ {
source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n"); source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
bool isFirstEntry = true; bool isFirstEntry = true;
foreach (var method in godotClassMethods) foreach (string methodName in distinctMethodNames)
{ {
GenerateHasMethodEntry(method, source, isFirstEntry); GenerateHasMethodEntry(methodName, source, isFirstEntry);
isFirstEntry = false; isFirstEntry = false;
} }
@ -203,91 +218,6 @@ namespace Godot.SourceGenerators
source.Append(" }\n"); source.Append(" }\n");
} }
// Generate RaiseGodotClassSignalCallbacks
if (godotSignalDelegates.Count > 0)
{
source.Append(
" protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
source.Append("NativeVariantPtrArgs args, int argCount)\n {\n");
foreach (var signal in godotSignalDelegates)
{
GenerateSignalEventInvoker(signal, source);
}
source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
source.Append(" }\n");
}
// Generate Set/GetGodotClassPropertyValue
if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
{
bool isFirstEntry;
// Setters
bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
if (!allPropertiesAreReadOnly)
{
source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
source.Append("in godot_variant value)\n {\n");
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
if (property.PropertySymbol.IsReadOnly)
continue;
GeneratePropertySetter(property.PropertySymbol.Name,
property.PropertySymbol.Type, property.Type, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
if (field.FieldSymbol.IsReadOnly)
continue;
GeneratePropertySetter(field.FieldSymbol.Name,
field.FieldSymbol.Type, field.Type, source, isFirstEntry);
isFirstEntry = false;
}
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");
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
GeneratePropertyGetter(property.PropertySymbol.Name,
property.Type, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
GeneratePropertyGetter(field.FieldSymbol.Name,
field.Type, source, isFirstEntry);
isFirstEntry = false;
}
source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
source.Append(" }\n");
}
source.Append("}\n"); // partial class source.Append("}\n"); // partial class
if (isInnerClass) if (isInnerClass)
@ -310,6 +240,121 @@ namespace Godot.SourceGenerators
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
} }
private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
{
source.Append(" methods.Add(new(name: GodotInternal.MethodName_")
.Append(methodInfo.Name)
.Append(", returnVal: ");
AppendPropertyInfo(source, methodInfo.ReturnVal);
source.Append(", flags: (Godot.MethodFlags)")
.Append((int)methodInfo.Flags)
.Append(", arguments: ");
if (methodInfo.Arguments is { Count: > 0 })
{
source.Append("new() { ");
foreach (var param in methodInfo.Arguments)
{
AppendPropertyInfo(source, param);
// C# allows colon after the last element
source.Append(", ");
}
source.Append(" }");
}
else
{
source.Append("null");
}
source.Append(", defaultArguments: null));\n");
}
private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
{
source.Append("new(type: (Godot.Variant.Type)")
.Append((int)propertyInfo.Type)
.Append(", name: \"")
.Append(propertyInfo.Name)
.Append("\", hint: (Godot.PropertyHint)")
.Append((int)propertyInfo.Hint)
.Append(", hintString: \"")
.Append(propertyInfo.HintString)
.Append("\", usage: (Godot.PropertyUsageFlags)")
.Append((int)propertyInfo.Usage)
.Append(", exported: ")
.Append(propertyInfo.Exported ? "true" : "false")
.Append(")");
}
private static MethodInfo DetermineMethodInfo(GodotMethodData method)
{
PropertyInfo returnVal;
if (method.RetType != null)
{
returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty);
}
else
{
returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
hintString: null, PropertyUsageFlags.Default, exported: false);
}
int paramCount = method.ParamTypes.Length;
List<PropertyInfo>? arguments;
if (paramCount > 0)
{
arguments = new(capacity: paramCount);
for (int i = 0; i < paramCount; i++)
{
arguments.Add(DeterminePropertyInfo(method.ParamTypes[i],
name: method.Method.Parameters[i].Name));
}
}
else
{
arguments = null;
}
return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments,
defaultArguments: null);
}
private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
{
var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
var propUsage = PropertyUsageFlags.Default;
if (memberVariantType == VariantType.Nil)
propUsage |= PropertyUsageFlags.NilIsVariant;
return new PropertyInfo(memberVariantType, name,
PropertyHint.None, string.Empty, propUsage, exported: false);
}
private static void GenerateHasMethodEntry(
string methodName,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (method == GodotInternal.MethodName_");
source.Append(methodName);
source.Append(") {\n return true;\n }\n");
}
private static void GenerateMethodInvoker( private static void GenerateMethodInvoker(
GodotMethodData method, GodotMethodData method,
StringBuilder source StringBuilder source
@ -359,105 +404,5 @@ namespace Godot.SourceGenerators
source.Append(" }\n"); source.Append(" }\n");
} }
private static void GenerateSignalEventInvoker(
GodotSignalDelegateData signal,
StringBuilder source
)
{
string signalName = signal.Name;
var invokeMethodData = signal.InvokeMethodData;
source.Append(" if (signal == GodotInternal.SignalName_");
source.Append(signalName);
source.Append(" && argCount == ");
source.Append(invokeMethodData.ParamTypes.Length);
source.Append(") {\n");
source.Append(" backing_");
source.Append(signalName);
source.Append("?.Invoke(");
for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
{
if (i != 0)
source.Append(", ");
source.AppendVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
}
source.Append(");\n");
source.Append(" return;\n");
source.Append(" }\n");
}
private static void GeneratePropertySetter(
string propertyMemberName,
ITypeSymbol propertyTypeSymbol,
MarshalType propertyMarshalType,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (name == GodotInternal.PropName_")
.Append(propertyMemberName)
.Append(") {\n")
.Append(" ")
.Append(propertyMemberName)
.Append(" = ")
.AppendVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
}
private static void GeneratePropertyGetter(
string propertyMemberName,
MarshalType propertyMarshalType,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (name == GodotInternal.PropName_")
.Append(propertyMemberName)
.Append(") {\n")
.Append(" value = ")
.AppendManagedToVariantExpr(propertyMemberName, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
}
private static void GenerateHasMethodEntry(
GodotMethodData method,
StringBuilder source,
bool isFirstEntry
)
{
string methodName = method.Method.Name;
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (method == GodotInternal.MethodName_");
source.Append(methodName);
source.Append(") {\n return true;\n }\n");
}
public void Initialize(GeneratorInitializationContext context)
{
}
} }
} }

View File

@ -146,10 +146,72 @@ namespace Godot.SourceGenerators
source.Append(" }\n"); // class GodotInternal source.Append(" }\n"); // class GodotInternal
// Generate GetGodotPropertyList
if (godotClassProperties.Length > 0 || godotClassFields.Length > 0) if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
{ {
bool isFirstEntry;
// Generate SetGodotClassPropertyValue
bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
if (!allPropertiesAreReadOnly)
{
source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
source.Append("in godot_variant value)\n {\n");
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
if (property.PropertySymbol.IsReadOnly)
continue;
GeneratePropertySetter(property.PropertySymbol.Name,
property.PropertySymbol.Type, property.Type, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
if (field.FieldSymbol.IsReadOnly)
continue;
GeneratePropertySetter(field.FieldSymbol.Name,
field.FieldSymbol.Type, field.Type, source, isFirstEntry);
isFirstEntry = false;
}
source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
source.Append(" }\n");
}
// Generate GetGodotClassPropertyValue
source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
source.Append("out godot_variant value)\n {\n");
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
GeneratePropertyGetter(property.PropertySymbol.Name,
property.Type, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
GeneratePropertyGetter(field.FieldSymbol.Name,
field.Type, source, isFirstEntry);
isFirstEntry = false;
}
source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
source.Append(" }\n");
// Generate GetGodotPropertyList
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>";
@ -212,6 +274,53 @@ namespace Godot.SourceGenerators
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
} }
private static void GeneratePropertySetter(
string propertyMemberName,
ITypeSymbol propertyTypeSymbol,
MarshalType propertyMarshalType,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (name == GodotInternal.PropName_")
.Append(propertyMemberName)
.Append(") {\n")
.Append(" ")
.Append(propertyMemberName)
.Append(" = ")
.AppendVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
}
private static void GeneratePropertyGetter(
string propertyMemberName,
MarshalType propertyMarshalType,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (name == GodotInternal.PropName_")
.Append(propertyMemberName)
.Append(") {\n")
.Append(" value = ")
.AppendManagedToVariantExpr(propertyMemberName, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
}
private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
{ {
source.Append(" properties.Add(new(type: (Godot.Variant.Type)") source.Append(" properties.Add(new(type: (Godot.Variant.Type)")

View File

@ -232,6 +232,24 @@ namespace Godot.SourceGenerators
.Append("}\n"); .Append("}\n");
} }
// Generate RaiseGodotClassSignalCallbacks
if (godotSignalDelegates.Count > 0)
{
source.Append(
" protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
source.Append("NativeVariantPtrArgs args, int argCount)\n {\n");
foreach (var signal in godotSignalDelegates)
{
GenerateSignalEventInvoker(signal, source);
}
source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
source.Append(" }\n");
}
source.Append("}\n"); // partial class source.Append("}\n"); // partial class
if (isInnerClass) if (isInnerClass)
@ -356,5 +374,38 @@ namespace Godot.SourceGenerators
return new PropertyInfo(memberVariantType, name, return new PropertyInfo(memberVariantType, name,
PropertyHint.None, string.Empty, propUsage, exported: false); PropertyHint.None, string.Empty, propUsage, exported: false);
} }
private static void GenerateSignalEventInvoker(
GodotSignalDelegateData signal,
StringBuilder source
)
{
string signalName = signal.Name;
var invokeMethodData = signal.InvokeMethodData;
source.Append(" if (signal == GodotInternal.SignalName_");
source.Append(signalName);
source.Append(" && argCount == ");
source.Append(invokeMethodData.ParamTypes.Length);
source.Append(") {\n");
source.Append(" backing_");
source.Append(signalName);
source.Append("?.Invoke(");
for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
{
if (i != 0)
source.Append(", ");
source.AppendVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
}
source.Append(");\n");
source.Append(" return;\n");
source.Append(" }\n");
}
} }
} }

View File

@ -24,7 +24,7 @@ namespace Godot.Bridge
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath; public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge; public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass; public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; public delegate* unmanaged<IntPtr, godot_bool*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType; public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList; public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues; public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;

View File

@ -557,7 +557,8 @@ namespace Godot.Bridge
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool, internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript) godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
{ {
try try
{ {
@ -578,12 +579,58 @@ namespace Godot.Bridge
if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools") if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
*outTool = godot_bool.True; *outTool = godot_bool.True;
// Methods
// Performance is not critical here as this will be replaced with source generators.
using var methods = new Collections.Array();
Type? top = scriptType;
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native)
{
var methodList = GetMethodListForType(top);
if (methodList != null)
{
foreach (var method in methodList)
{
var methodInfo = new Collections.Dictionary();
methodInfo.Add("name", method.Name);
var methodParams = new Collections.Array();
if (method.Arguments != null)
{
foreach (var param in method.Arguments)
{
methodParams.Add(new Collections.Dictionary()
{
{ "name", param.Name },
{ "type", param.Type },
{ "usage", param.Usage }
});
}
}
methodInfo.Add("params", methodParams);
methods.Add(methodInfo);
}
}
top = top.BaseType;
}
*outMethodsDest = NativeFuncs.godotsharp_array_new_copy(
(godot_array)methods.NativeValue);
// RPC functions // RPC functions
Collections.Dictionary<string, Collections.Dictionary> rpcFunctions = new(); Collections.Dictionary<string, Collections.Dictionary> rpcFunctions = new();
Type? top = scriptType; top = scriptType;
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native) while (top != null && top != native)
{ {
@ -617,9 +664,8 @@ namespace Godot.Bridge
top = top.BaseType; top = top.BaseType;
} }
*outRpcFunctionsDest = *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new_copy(
NativeFuncs.godotsharp_dictionary_new_copy( (godot_dictionary)((Collections.Dictionary)rpcFunctions).NativeValue);
(godot_dictionary)((Collections.Dictionary)rpcFunctions).NativeValue);
// Event signals // Event signals
@ -663,8 +709,8 @@ namespace Godot.Bridge
top = top.BaseType; top = top.BaseType;
} }
*outEventSignalsDest = *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new_copy(
NativeFuncs.godotsharp_dictionary_new_copy((godot_dictionary)signals.NativeValue); (godot_dictionary)signals.NativeValue);
// Base script // Base script
@ -701,6 +747,19 @@ namespace Godot.Bridge
return (List<MethodInfo>?)getGodotSignalListMethod.Invoke(null, null); return (List<MethodInfo>?)getGodotSignalListMethod.Invoke(null, null);
} }
private static List<MethodInfo>? GetMethodListForType(Type type)
{
var getGodotMethodListMethod = type.GetMethod(
"GetGodotMethodList",
BindingFlags.DeclaredOnly | BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public);
if (getGodotMethodListMethod == null)
return null;
return (List<MethodInfo>?)getGodotMethodListMethod.Invoke(null, null);
}
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
[SuppressMessage("ReSharper", "NotAccessedField.Local")] [SuppressMessage("ReSharper", "NotAccessedField.Local")]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]

View File

@ -87,7 +87,7 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *); using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *); using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *); using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *, Dictionary *, Ref<CSharpScript> *); using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool); using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add); using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add); using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);