Delsin-Yu / CSharp-Wrapper-Generator-for-GDExtension

This editor plugin generates C# wrappers for classes from the GDExtension plugins.
MIT License
11 stars 2 forks source link

[GDMP] Problem MediaPipeFaceLandmarker.Instantiate() #18

Closed GeorgeS2019 closed 2 months ago

GeorgeS2019 commented 2 months ago

Need feedback how to debug this error.

ERROR: System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type 'Godot.ScriptPathAttribute' found.
   at System.Attribute.GetCustomAttribute(MemberInfo element, Type attributeType, Boolean inherit)
   at System.Reflection.CustomAttributeExtensions.GetCustomAttribute[T](MemberInfo element)
   at GDExtensionHelper.Bind[T](GodotObject godotObject) in _GDExtensionHelper.cs:line 38
   at GDExtensionHelper.Instantiate[T](StringName className) in _GDExtensionHelper.cs:line 48
   at GDExtension.Wrappers.MediaPipeFaceLandmarker.Instantiate() in MediaPipeFaceLandmarker.cs:line 19

   at GDExtension.Wrappers.MediaPipeCameraHelper.<add_NewFrame>b__15_0(Variant arg0_variant) in MediaPipeCameraHelper.cs:line 93
   at Godot.Callable.<From>g__Trampoline|2_0[T0](Object delegateObj, NativeVariantPtrArgs args, godot_variant& ret) in godot\modules\mono\glue\GodotSharp\GodotSharp\Core\Callable.generics.cs:line 56
   at Godot.DelegateUtils.InvokeWithVariantArgs(IntPtr delegateGCHandle, Void* trampoline, godot_variant** args, Int32 argc, godot_variant* outRet) in godot\modules\mono\glue\GodotSharp\GodotSharp\Core\DelegateUtils.cs:line 86
   at: void Godot.NativeInterop.ExceptionUtils.LogException(System.Exception) godot/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs:113)
Delsin-Yu commented 2 months ago

Please provide an MRP to help us reproduce this issue.

GeorgeS2019 commented 2 months ago

GDExtensionHelper.cs

using System.Reflection;
using Godot;

public static class GDExtensionHelper
{
    private static readonly System.Collections.Generic.Dictionary<string, GodotObject> _instances = [];

    /// <summary>
    /// Calls a static method within the given type.
    /// </summary>
    /// <param name="className">The type name.</param>
    /// <param name="method">The method name.</param>
    /// <param name="arguments">The arguments.</param>
    /// <returns>The return value of the method.</returns>
    public static Variant Call(string className, string method, params Variant[] arguments)
    {
        if (!_instances.TryGetValue(className, out var instance))
        {
            instance = ClassDB.Instantiate(className).AsGodotObject();
            _instances[className] = instance;
        }

        return instance.Call(method, arguments);
    }

    /// <summary>
    /// Try to cast the script on the supplied <paramref name="godotObject"/> to the <typeparamref name="T"/> wrapper type,
    /// if no script has attached to the type, or the script attached to the type does not inherit the <typeparamref name="T"/> wrapper type,
    /// a new instance of the <typeparamref name="T"/> wrapper script will get attaches to the <paramref name="godotObject"/>.
    /// </summary>
    /// <remarks>The developer should only supply the <paramref name="godotObject"/> that represents the correct underlying GDExtension type.</remarks>
    /// <param name="godotObject">The <paramref name="godotObject"/> that represents the correct underlying GDExtension type.</param>
    /// <returns>The existing or a new instance of the <typeparamref name="T"/> wrapper script attached to the supplied <paramref name="godotObject"/>.</returns>
    public static T Bind<T>(GodotObject godotObject) where T : GodotObject
    {
        if (godotObject is T wrapperScript) return wrapperScript;
        var instanceId = godotObject.GetInstanceId();
        godotObject.SetScript(ResourceLoader.Load(typeof(T).GetCustomAttribute<ScriptPathAttribute>()!.Path));
        return (T)GodotObject.InstanceFromId(instanceId);
    }

    /// <summary>
    /// Creates an instance of the GDExtension <typeparam name="T"/> type, and attaches the wrapper script to it.
    /// </summary>
    /// <returns>The wrapper instance linked to the underlying GDExtension type.</returns>
    public static T Instantiate<T>(StringName className) where T : GodotObject
    {
        return Bind<T>(ClassDB.Instantiate(className).As<GodotObject>());
    }
}
GeorgeS2019 commented 2 months ago

Perhaps it is time to setup Unit tests to check if it is possible just to Instantiate()each created class.

MediaPipeFaceLandmarker.Instantiate()

https://github.com/Delsin-Yu/CSharp-Wrapper-Generator-for-GDExtension/issues/10

Delsin-Yu commented 2 months ago

That's a good point, but let's first fix this issue at lest.

GeorgeS2019 commented 2 months ago

@Delsin-Yu Overall, I am surprised how great the generated wrappers. I could get through, most time.

Delsin-Yu commented 2 months ago

I'm having trouble reproducing the issue you experienced on my end, please provide a sample project if possible.

GeorgeS2019 commented 2 months ago

https://github.com/j20001970/GDMP-demo/blob/3474994c3a844d935551c9793c8b642868105d2e/project/vision/face_landmarker/FaceLandmarker.gd#L17

        var task: MediaPipeFaceLandmarker
    task = MediaPipeFaceLandmarker.new()  #<=== this step fail
    task.initialize(base_options, running_mode, 1, 0.5, 0.5, 0.5, true)
    task.result_callback.connect(self._result_callback)

cs

MediaPipeFaceLandmarker task = MediaPipeFaceLandmarker.Instantiate(); 
GeorgeS2019 commented 2 months ago

Using the gdunit4mono test adapter, it is possible to load a specific scene ( attach to either GDScript or csharp ) . a quick way to check through porting of GDScript to Csharp

Delsin-Yu commented 2 months ago

I woundn't say porting is a quick way, but that's a good point. Anyway, I see the issue, working on it.

GeorgeS2019 commented 2 months ago

Multiple custom attributes of the same type 'Godot.ScriptPathAttribute' found.

The instantiate() of this class is probably called multiple times, more than once.

Perhaps there is a way to check that the class is not instantiated more than once.

Delsin-Yu commented 2 months ago

No, it's simply the issue of this line.

typeof(T).GetCustomAttribute<ScriptPathAttribute>()
GeorgeS2019 commented 2 months ago

No, it's simply the issue of this line.

Can you elaborate?

Delsin-Yu commented 2 months ago

MediaPipeFaceLandmarker is inheriting MediaPipeTask, both of them have ScriptPathAttributes, and GetCustomAttribute is having trouble on handling that.

GeorgeS2019 commented 2 months ago

Inheritance is an issue!

Delsin-Yu commented 2 months ago

Should work by now. https://github.com/Delsin-Yu/CSharp-Wrapper-Generator-for-GDExtension/commit/1808c030d1f810b54f1245a8782482a8ef85babf#diff-23079fb747dc41885852246b8e4f8d278554ece4f2d26f3f18b19c7da8901a77 image