Merge pull request #96955 from Delsin-Yu/generator-based-CreateManagedForGodotObjectBinding
[.NET] Replace Reflection-Based implementation with Generated one in `CreateManagedForGodotObjectBinding`
This commit is contained in:
commit
ea8d20d35b
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using GodotTools.Build;
|
using GodotTools.Build;
|
||||||
using GodotTools.Ides;
|
using GodotTools.Ides;
|
||||||
using GodotTools.Ides.Rider;
|
using GodotTools.Ides.Rider;
|
||||||
|
@ -701,6 +702,23 @@ namespace GodotTools
|
||||||
private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
|
private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
|
||||||
{
|
{
|
||||||
Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
|
Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
|
||||||
|
|
||||||
|
var populateConstructorMethod =
|
||||||
|
AppDomain.CurrentDomain
|
||||||
|
.GetAssemblies()
|
||||||
|
.First(x => x.GetName().Name == "GodotSharpEditor")
|
||||||
|
.GetType("Godot.EditorConstructors")?
|
||||||
|
.GetMethod("AddEditorConstructors",
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
|
||||||
|
if (populateConstructorMethod == null)
|
||||||
|
{
|
||||||
|
throw new MissingMethodException("Godot.EditorConstructors",
|
||||||
|
"AddEditorConstructors");
|
||||||
|
}
|
||||||
|
|
||||||
|
populateConstructorMethod.Invoke(null, null);
|
||||||
|
|
||||||
return new GodotSharpEditor().NativeInstance;
|
return new GodotSharpEditor().NativeInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,10 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
|
||||||
#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
|
#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
|
||||||
#define BINDINGS_NATIVE_NAME_FIELD "NativeName"
|
#define BINDINGS_NATIVE_NAME_FIELD "NativeName"
|
||||||
|
|
||||||
|
#define BINDINGS_CLASS_CONSTRUCTOR "Constructors"
|
||||||
|
#define BINDINGS_CLASS_CONSTRUCTOR_EDITOR "EditorConstructors"
|
||||||
|
#define BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY "BuiltInMethodConstructors"
|
||||||
|
|
||||||
#define CS_PARAM_MEMORYOWN "memoryOwn"
|
#define CS_PARAM_MEMORYOWN "memoryOwn"
|
||||||
#define CS_PARAM_METHODBIND "method"
|
#define CS_PARAM_METHODBIND "method"
|
||||||
#define CS_PARAM_INSTANCE "ptr"
|
#define CS_PARAM_INSTANCE "ptr"
|
||||||
|
@ -1737,6 +1741,69 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
|
||||||
compile_items.push_back(output_file);
|
compile_items.push_back(output_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate source file for built-in type constructor dictionary.
|
||||||
|
|
||||||
|
{
|
||||||
|
StringBuilder cs_built_in_ctors_content;
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
|
||||||
|
cs_built_in_ctors_content.append("using System;\n"
|
||||||
|
"using System.Collections.Generic;\n"
|
||||||
|
"\n");
|
||||||
|
cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR "\n{");
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(MEMBER_BEGIN "internal static readonly Dictionary<string, Func<IntPtr, GodotObject>> " BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(MEMBER_BEGIN "public static GodotObject Invoke(string nativeTypeNameStr, IntPtr nativeObjectPtr)\n");
|
||||||
|
cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
|
||||||
|
cs_built_in_ctors_content.append(INDENT2 "if (!" BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".TryGetValue(nativeTypeNameStr, out var constructor))\n");
|
||||||
|
cs_built_in_ctors_content.append(INDENT3 "throw new InvalidOperationException(\"Wrapper class not found for type: \" + nativeTypeNameStr);\n");
|
||||||
|
cs_built_in_ctors_content.append(INDENT2 "return constructor(nativeObjectPtr);\n");
|
||||||
|
cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(MEMBER_BEGIN "static " BINDINGS_CLASS_CONSTRUCTOR "()\n");
|
||||||
|
cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
|
||||||
|
cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY " = new();\n");
|
||||||
|
|
||||||
|
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
|
||||||
|
const TypeInterface &itype = E.value;
|
||||||
|
|
||||||
|
if (itype.api_type != ClassDB::API_CORE || itype.is_singleton_instance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itype.is_deprecated) {
|
||||||
|
cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".Add(\"");
|
||||||
|
cs_built_in_ctors_content.append(itype.name);
|
||||||
|
cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");
|
||||||
|
cs_built_in_ctors_content.append(itype.proxy_name);
|
||||||
|
if (itype.is_singleton && !itype.is_compat_singleton) {
|
||||||
|
cs_built_in_ctors_content.append("Instance");
|
||||||
|
}
|
||||||
|
cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");
|
||||||
|
|
||||||
|
if (itype.is_deprecated) {
|
||||||
|
cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(CLOSE_BLOCK);
|
||||||
|
|
||||||
|
String constructors_file = path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR ".cs");
|
||||||
|
Error err = _save_file(constructors_file, cs_built_in_ctors_content);
|
||||||
|
|
||||||
|
if (err != OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_items.push_back(constructors_file);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate native calls
|
// Generate native calls
|
||||||
|
|
||||||
StringBuilder cs_icalls_content;
|
StringBuilder cs_icalls_content;
|
||||||
|
@ -1844,6 +1911,57 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
|
||||||
compile_items.push_back(output_file);
|
compile_items.push_back(output_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate source file for editor type constructor dictionary.
|
||||||
|
|
||||||
|
{
|
||||||
|
StringBuilder cs_built_in_ctors_content;
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
|
||||||
|
cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR_EDITOR "\n{");
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(MEMBER_BEGIN "private static void AddEditorConstructors()\n");
|
||||||
|
cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
|
||||||
|
cs_built_in_ctors_content.append(INDENT2 "var builtInMethodConstructors = " BINDINGS_CLASS_CONSTRUCTOR "." BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");
|
||||||
|
|
||||||
|
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
|
||||||
|
const TypeInterface &itype = E.value;
|
||||||
|
|
||||||
|
if (itype.api_type != ClassDB::API_EDITOR || itype.is_singleton_instance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itype.is_deprecated) {
|
||||||
|
cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(INDENT2 "builtInMethodConstructors.Add(\"");
|
||||||
|
cs_built_in_ctors_content.append(itype.name);
|
||||||
|
cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");
|
||||||
|
cs_built_in_ctors_content.append(itype.proxy_name);
|
||||||
|
if (itype.is_singleton && !itype.is_compat_singleton) {
|
||||||
|
cs_built_in_ctors_content.append("Instance");
|
||||||
|
}
|
||||||
|
cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");
|
||||||
|
|
||||||
|
if (itype.is_deprecated) {
|
||||||
|
cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
|
||||||
|
|
||||||
|
cs_built_in_ctors_content.append(CLOSE_BLOCK);
|
||||||
|
|
||||||
|
String constructors_file = path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR_EDITOR ".cs");
|
||||||
|
Error err = _save_file(constructors_file, cs_built_in_ctors_content);
|
||||||
|
|
||||||
|
if (err != OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_items.push_back(constructors_file);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate native calls
|
// Generate native calls
|
||||||
|
|
||||||
StringBuilder cs_icalls_content;
|
StringBuilder cs_icalls_content;
|
||||||
|
@ -2210,6 +2328,15 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
|
||||||
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
|
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output << MEMBER_BEGIN "internal " << itype.proxy_name << "(IntPtr " CS_PARAM_INSTANCE ") : this("
|
||||||
|
<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1
|
||||||
|
<< INDENT2 "NativePtr = " CS_PARAM_INSTANCE ";\n"
|
||||||
|
<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK
|
||||||
|
<< INDENT3 "ConstructAndInitialize(null, "
|
||||||
|
<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
|
||||||
|
<< (itype.is_ref_counted ? "true" : "false") << ");\n"
|
||||||
|
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
|
||||||
|
|
||||||
// Add.. em.. trick constructor. Sort of.
|
// Add.. em.. trick constructor. Sort of.
|
||||||
output.append(MEMBER_BEGIN "internal ");
|
output.append(MEMBER_BEGIN "internal ");
|
||||||
output.append(itype.proxy_name);
|
output.append(itype.proxy_name);
|
||||||
|
|
|
@ -93,27 +93,15 @@ namespace Godot.Bridge
|
||||||
internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName,
|
internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName,
|
||||||
IntPtr godotObject)
|
IntPtr godotObject)
|
||||||
{
|
{
|
||||||
// TODO: Optimize with source generators and delegate pointers.
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
|
using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
|
||||||
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName)));
|
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName)));
|
||||||
string nativeTypeNameStr = stringName.ToString();
|
string nativeTypeNameStr = stringName.ToString();
|
||||||
|
|
||||||
Type nativeType = TypeGetProxyClass(nativeTypeNameStr) ?? throw new InvalidOperationException(
|
var instance = Constructors.Invoke(nativeTypeNameStr, godotObject);
|
||||||
"Wrapper class not found for type: " + nativeTypeNameStr);
|
|
||||||
var obj = (GodotObject)FormatterServices.GetUninitializedObject(nativeType);
|
|
||||||
|
|
||||||
var ctor = nativeType.GetConstructor(
|
return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(instance));
|
||||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
|
||||||
null, Type.EmptyTypes, null);
|
|
||||||
|
|
||||||
obj.NativePtr = godotObject;
|
|
||||||
|
|
||||||
_ = ctor!.Invoke(obj, null);
|
|
||||||
|
|
||||||
return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj));
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -308,66 +296,6 @@ namespace Godot.Bridge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type? TypeGetProxyClass(string nativeTypeNameStr)
|
|
||||||
{
|
|
||||||
// Performance is not critical here as this will be replaced with a generated dictionary.
|
|
||||||
|
|
||||||
if (nativeTypeNameStr[0] == '_')
|
|
||||||
nativeTypeNameStr = nativeTypeNameStr.Substring(1);
|
|
||||||
|
|
||||||
Type? wrapperType = typeof(GodotObject).Assembly.GetType("Godot." + nativeTypeNameStr);
|
|
||||||
|
|
||||||
if (wrapperType == null)
|
|
||||||
{
|
|
||||||
wrapperType = GetTypeByGodotClassAttr(typeof(GodotObject).Assembly, nativeTypeNameStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wrapperType == null)
|
|
||||||
{
|
|
||||||
var editorAssembly = AppDomain.CurrentDomain.GetAssemblies()
|
|
||||||
.FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor");
|
|
||||||
|
|
||||||
if (editorAssembly != null)
|
|
||||||
{
|
|
||||||
wrapperType = editorAssembly.GetType("Godot." + nativeTypeNameStr);
|
|
||||||
|
|
||||||
if (wrapperType == null)
|
|
||||||
{
|
|
||||||
wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Type? GetTypeByGodotClassAttr(Assembly assembly, string nativeTypeNameStr)
|
|
||||||
{
|
|
||||||
var types = assembly.GetTypes();
|
|
||||||
foreach (var type in types)
|
|
||||||
{
|
|
||||||
var attr = type.GetCustomAttribute<GodotClassNameAttribute>();
|
|
||||||
if (attr?.Name == nativeTypeNameStr)
|
|
||||||
{
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed;
|
|
||||||
|
|
||||||
if (wrapperType != null && IsStatic(wrapperType))
|
|
||||||
{
|
|
||||||
// A static class means this is a Godot singleton class. Try to get the Instance proxy type.
|
|
||||||
wrapperType = TypeGetProxyClass($"{wrapperType.Name}Instance");
|
|
||||||
if (wrapperType == null)
|
|
||||||
{
|
|
||||||
// Otherwise, fallback to GodotObject.
|
|
||||||
return typeof(GodotObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapperType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called from GodotPlugins
|
// Called from GodotPlugins
|
||||||
// ReSharper disable once UnusedMember.Local
|
// ReSharper disable once UnusedMember.Local
|
||||||
public static void LookupScriptsInAssembly(Assembly assembly)
|
public static void LookupScriptsInAssembly(Assembly assembly)
|
||||||
|
|
|
@ -29,6 +29,17 @@ namespace Godot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal GodotObject(IntPtr nativePtr) : this(false)
|
||||||
|
{
|
||||||
|
// NativePtr must be non-zero before calling ConstructAndInitialize to avoid invoking the constructor NativeCtor.
|
||||||
|
// We don't want to invoke the constructor, because we already have a constructed instance in nativePtr.
|
||||||
|
NativePtr = nativePtr;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
ConstructAndInitialize(NativeCtor, NativeName, _cachedType, refCounted: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal unsafe void ConstructAndInitialize(
|
internal unsafe void ConstructAndInitialize(
|
||||||
delegate* unmanaged<godot_bool, IntPtr> nativeCtor,
|
delegate* unmanaged<godot_bool, IntPtr> nativeCtor,
|
||||||
StringName nativeName,
|
StringName nativeName,
|
||||||
|
|
Loading…
Reference in New Issue