Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a struct-like type that can be exposed to scripting. #82198

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

nlupugla
Copy link
Contributor

@nlupugla nlupugla commented Sep 23, 2023

I am in the process of implementing the proposal godotengine/godot-proposals#7329 of @reduz on adding structs to GDScript. This work is still very much in it's early stages and many features still have yet to be implemented. Nevertheless, I'm making the draft public now so I can get continual feedback on this highly requested feature.

Edit 2023-Oct-18 22:50: Added list of structable methods

I've gone through Godot's API and made a list of about 130 methods that might benefit from structification: https://gist.github.com/nlupugla/01d59094d5fa31230e15bd991b63d33f.

  • Add struct attributes to the Array class.
  • Create Struct<T> class as a child of Array.
  • Create STRUCT_LAYOUT and related macros to facilitate creating Struct<T> specializations from actual C++ structs.
  • Proof of concept: create a struct that is equivalent to the PropertyInfo used in Object::get_property_list.
  • Implement type validation.
  • Find a way to return a typed array of structs (Godot does not yet support TypedArray<TypedArray<T>>).
  • Register structs with ClassDB.
  • Proof of concept: expose a struct to GDScript.
  • Comprehensive unit tests.
  • Performance profiling.
  • Performance optimizations.
  • Probably a million other things I'm forgetting.
  • Implement struct syntax in GDScript.
  • Implement static analysis for structs in GDScript.
  • Figure out how structs will interoperate with C# and GDExtension.
  • User testing.

core/object/object.cpp Outdated Show resolved Hide resolved
core/variant/array.cpp Outdated Show resolved Hide resolved
core/variant/struct.h Outdated Show resolved Hide resolved
/// structs

uint32_t struct_size = 0;
const StructMember *struct_members = nullptr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you are trying to simplify the code, but this should work like the proposal for performance reasons. With separate arrays, this means less cache lines loaded for the lookup, hence faster performance.

#define STRUCT_MEMBER(m_name, m_type) StructMember(SNAME(m_name), m_type)
#define STRUCT_CLASS_MEMBER(m_name, m_class) StructMember(SNAME(m_name), Variant::OBJECT, m_class)
// TODO: is there a way to define this so that the member count doesn't have to be passed?
#define STRUCT_LAYOUT(m_name, m_member_count, ...) \
Copy link
Member

@reduz reduz Sep 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think passing member count is unnecesary, you can add some macro magic to guess the number of `VA_ARGS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out the proper way to guess the number of args. One method I saw worked using sizeof, but I don't think that will work here because sizeof StructMember won't be known yet? Another method involved explicitly enumerating all possible number of arguments up to some finite number (like 16, or 39, or whatever you have the patience to write). That method seemed pretty clunky to me, and would also limit the number of members a struct can have. That said, it would be great if the user didn't have to pass in the length somehow.

Copy link

@adrian17 adrian17 Sep 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could check Boost.preprocessor for inspiration, there the macro would probably look more like (note lack of delimiting semicolons)

STRUCT_LAYOUT(PropertyInfoLayout,
		("name", Variant::STRING)
		("type", Variant::INT)
		("hint", Variant::INT)
		("hint_string", Variant::STRING)
		("class_name", Variant::STRING_NAME)
);

At which point BOOST_PP_SEQ_SIZE would give you the size. (and it's more generic in general, in case you ever needed a macro iterate over the member list more than once, for example to generate get/set_hint_string etc instead of having C++ call get/set_named). I've seen a macro that did a very similar struct declaration exactly this way (not in public code though).
Or instead of getting size via a macro, you could put the field types into a tuple type and get tuple size at compile time via templates.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip! We won't to avoid unnecessary packages and libraries though, so tuple is off limits. A quick search on how BOOST_PP_SEQ_SIZE works suggests it falls in to the camp of explicitly enumerating sequence lengths up to some finite number. Maybe that's the way to go in the end, but it feels a little odd to restrict the number of members in a struct to a smallish number (like 64 or 256).

Copy link

@adrian17 adrian17 Sep 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We won't to avoid unnecessary packages and libraries though, so tuple is off limits.

Sure, but even without an std::tuple, it still can be done about the same, with the sizeof... operator. So as long as you can get the macro to generate SomeTemplate<Variant::STRING, Variant::INT, Variant::INT, etc>, that should be enough. (...though at that point there are probably even simpler ways)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologise if I'm missing something, but if you moved up the static const StructMember members[member_count] = line, couldn't you get the member count with the classic sizeof(members)/sizeof(members[0])?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, if you changed it from static const StructMember members[member_count] = to static const StructMember members[] =, that is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason I thought that wouldn't work because StructMember isn't a primitive type (like int or something). I can try it out though, it would certainly be much simpler :)

Copy link
Contributor

@hikari-no-yume hikari-no-yume Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, when I try it I get an error about “in-class initialization of static data member 'const StructMember PropertyInfoLayout::members []' of incomplete type” (https://godbolt.org/z/vG8csqqjd). I'm not sure why C++ cares about that. It does work however if it is made a static variable (outside the class) rather than a static property. To namespace it you could use token concatenation like: const StructMember m_name##_members[] = { (https://godbolt.org/z/9P5aGGcfs) but I appreciate that might not be to everyone's taste.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, your idea seems to be working for me :)

// Added struct stuff:
uint32_t struct_size = 0;
StringName * struct_member_names = nullptr;
bool struct_array = false;
Copy link
Contributor Author

@nlupugla nlupugla Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this redundant with the function is_struct_array()?

}

_FORCE_INLINE_ bool validate_member(uint32_t p_index,const Variant& p_value) {
// needs to check with ContainerValidate, return true is valid
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented some methods in ContainerValidate, but I'm still not entirely sure what to put here.



template <class T>
class Struct : public Array {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Struct inheriting from Array is kind of awkward. There are many Array methods that need to be manually disabled for Struct (such as push_back) and several Array methods that just don't make sense for Struct (like map). I understand the desire to not make another Variant::Type, as this would have implications for the whole codebase. I wonder whether it would make sense for Struct to not inherit from Array, but still call itself a Variant::Type::ARRAY as far as the rest of the codebase is concerned.


// The idea here is that if GDScript code is typed, it should be able to access everything without any kind of validation or even copies. I will add this in the GDScript optimization proposal I have soon (pointer addressing mode).

// That said, I think we should consider changing ArrayPrivate::Array from Vector to LocalVector, this should enormously improve performance when accessing untyped (And eventually typed) arrays in GDScript. Arrays are shared, so there is not much of a need to use Vector<> here.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have wondered about that too, though I feel it is beyond the scope of this PR.

Comment on lines 193 to 203
#define STRUCT_LAYOUT(m_class,m_name,...) \
struct m_name { \
_FORCE_INLINE_ static StringName get_class() { return SNAME(#m_class)); }
_FORCE_INLINE_ static StringName get_name() { return SNAME(#m_name)); }
static constexpr uint32_t member_count = GET_ARGUMENT_COUNT;\
_FORCE_INLINE_ static const StructMember& get_member(uint32_t p_index) {\
CRASH_BAD_INDEX(p_index,member_count)\
static StructMember members[member_count]={ __VA_ARGS__ };\
return members[p_index];\
}\
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an alternative proposal that I think will be cleaner and have several advantages. I've written it up here: https://gist.github.com/nlupugla/f78a947f0f2d409a7ab7819d5d379a28

@@ -64,7 +64,7 @@ struct _ObjectDebugLock {

#endif

STRUCT_LAYOUT(PropertyInfoLayout, "PropertyInfo", 5,
STRUCT_LAYOUT(PropertyInfoLayout, "PropertyInfo",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go in the .h I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting it in the .h makes things messy because then object.h has to include struct.h (where STRUCT_LAYOUT is defined) and now most of the engine suddenly depends on struct.h. Keeping this part in object.cpp keeps struct.h much more isolated.

Copy link
Contributor Author

@nlupugla nlupugla Sep 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Well I just now realized that putting the definition of Struct<PropertyInfoLayout> in object.cpp means that anyone that wants to use Struct<PropertyInfoLayout> has to include object.cpp, which is not good. Maybe the STRUCT_LAYOUT macro should go somewhere more central than struct.h? What would make sense? type_defs.h? type_info.h?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no, I think the solution is to just put it in object.h or make another filed in the variant folder called struct_layout.h. I was getting issues with circular dependencies if I tried to put the definition in typed_defs.h or type_info.h.

static const Variant::Type VARIANT_TYPE = Variant::ARRAY;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
_FORCE_INLINE_ static PropertyInfo get_class_info() {
return PropertyInfo(Variant::ARRAY, String(), PROPERTY_HINT_ARRAY_TYPE, T::get_class_static());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you will probably need to do it like this:

PROPERTY_HINT_STRUCT_TYPE, String(T::get_class())+"."+T::get_name());

PROPERTY_HINT_STRUCT_TYPE will need to be added in object.h

Copy link
Member

@reduz reduz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, left a comment

@nlupugla
Copy link
Contributor Author

nlupugla commented Oct 9, 2023

Edited my OP to link to a list of potentially structable methods in Godot's API: https://gist.github.com/nlupugla/01d59094d5fa31230e15bd991b63d33f.

Copy link
Contributor

@dsnopek dsnopek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As requested on RocketChat, I skimmed through the code related to compat methods for GDExtension, and left a few comments. I haven't had a chance to really dig into this PR, so it's possible my comments are missing some important context.

The CI does a great job of pointing out GDExtension compat issues, so I've started a CI run and we can see if it shows any issues.

#include "core/object/class_db.h"
#include "core/variant/typed_array.h"

PropertyInfo::operator Dictionary() const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few functions in here that aren't compat methods, including this one. These should probably just go in the normal object.cpp file?

return mi;
}

void Object::_add_user_signal_compat_99999(const String &p_name, const Array &p_args) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 99999 should be relplaced with 82198 - the number of this PR

Comment on lines 221 to 223
operator Dictionary() const;

static PropertyInfo from_dict(const Dictionary &p_dict);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure these can be removed when deprecated stuff is disabled? These are used in script_language_extension.h in methods that are definitely not deprecated. But I haven't looked through the whole PR to see if you've worked around that somewhere.

@@ -204,7 +264,9 @@ struct PropertyInfo {
}
};

#ifndef DISABLE_DEPRECATED
TypedArray<Dictionary> convert_property_list(const List<PropertyInfo> *p_list);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here: this is used in rendering_server.cpp in a method that isn't deprecated.

@nlupugla
Copy link
Contributor Author

As requested on RocketChat, I skimmed through the code related to compat methods for GDExtension, and left a few comments. I haven't had a chance to really dig into this PR, so it's possible my comments are missing some important context.

The CI does a great job of pointing out GDExtension compat issues, so I've started a CI run and we can see if it shows any issues.

Thanks dsnopek, really appreciate it!

So I'm getting from you that only the exposed methods need to go in the .inc file and the rest can live in the .h or .cpp file, is that right? And it sounds like the approach is that DISABLE_DEPRECATED should basically just change the external API, so internal things like the dictionary conversion methods that I "deprecated" should actually stick around so I don't have to go through and update the entire codebase (in places like script_language_extension as you mentioned) to use the new struct format instead.

@dsnopek
Copy link
Contributor

dsnopek commented Oct 10, 2023

Yep, that sounds about right!

@nlupugla
Copy link
Contributor Author

Great, that makes sense!

I made the suggested changes, but I'm still failing some of the GDScript unit tests. I'm not entirely sure why, but at least one test is failing because the test is calling get_property_list and expecting an array of dictionaries instead of an array of structs. Obviously, I could change the tests, but I think if I do the compatibility right, I shouldn't have to do that, right? Is there a way for me to tell the tests to use the compatibility methods?

@dsnopek
Copy link
Contributor

dsnopek commented Oct 10, 2023

Obviously, I could change the tests, but I think if I do the compatibility right, I shouldn't have to do that, right? Is there a way for me to tell the tests to use the compatibility methods?

The compatibility methods are only for GDExtension. For GDScript stuff you'll have to handle compatibility in a different way.

@nlupugla
Copy link
Contributor Author

Ah, interesting! Well, I guess that's an argument for moving GDScript to being a GDExtension :)

core/object/class_db.h Outdated Show resolved Hide resolved
core/core_bind.h Outdated
Comment on lines 462 to 504
#ifndef DISABLE_DEPRECATED
Dictionary class_get_signal_compat_82198(StringName p_class, StringName p_signal) const;
TypedArray<Dictionary> class_get_signal_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_property_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_method_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#ifndef DISABLE_DEPRECATED
Dictionary class_get_signal_compat_82198(StringName p_class, StringName p_signal) const;
TypedArray<Dictionary> class_get_signal_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_property_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_method_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
#endif

core/core_bind.h Outdated
@@ -423,6 +423,7 @@ class ClassDB : public Object {

protected:
static void _bind_methods();
static void _bind_compatibility_methods();
Copy link
Member

@AThousandShips AThousandShips Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void _bind_compatibility_methods();
#ifndef DISABLE_DEPRECATED
Dictionary _class_get_signal_compat_82198(StringName p_class, StringName p_signal) const;
TypedArray<Dictionary> _class_get_signal_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> _class_get_property_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> _class_get_method_list_compat_82198(StringName p_class, bool p_no_inheritance = false) const;
#endif
static void _bind_compatibility_methods();

These should be kept together

ClassDB::bind_compatibility_method(D_METHOD("get_incoming_connections"), &Object::_get_incoming_connections_compat_82198);
}

#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#endif
#endif

}
}

#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#endif
#endif

core/object/object.cpp Outdated Show resolved Hide resolved
core/object/object.cpp Outdated Show resolved Hide resolved
Comment on lines 716 to 425
#ifndef DISABLE_DEPRECATED
void _add_user_signal_compat_82198(const String &p_name, const Array &p_args = Array());
TypedArray<Dictionary> _get_signal_list_compat_82198() const;
TypedArray<Dictionary> _get_signal_connection_list_compat_82198(const StringName &p_signal) const;
TypedArray<Dictionary> _get_incoming_connections_compat_82198() const;
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#ifndef DISABLE_DEPRECATED
void _add_user_signal_compat_82198(const String &p_name, const Array &p_args = Array());
TypedArray<Dictionary> _get_signal_list_compat_82198() const;
TypedArray<Dictionary> _get_signal_connection_list_compat_82198(const StringName &p_signal) const;
TypedArray<Dictionary> _get_incoming_connections_compat_82198() const;
#endif

@@ -688,7 +774,8 @@ class Object {
virtual void _notificationv(int p_notification, bool p_reversed) {}

static void _bind_methods();
static void _bind_compatibility_methods() {}
static void _bind_compatibility_methods();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void _bind_compatibility_methods();
#ifndef DISABLE_DEPRECATED
void _add_user_signal_compat_82198(const String &p_name, const Array &p_args = Array());
TypedArray<Dictionary> _get_signal_list_compat_82198() const;
TypedArray<Dictionary> _get_signal_connection_list_compat_82198(const StringName &p_signal) const;
TypedArray<Dictionary> _get_incoming_connections_compat_82198() const;
TypedArray<Dictionary> _get_property_list_bind_compat_82198() const;
TypedArray<Dictionary> _get_method_list_bind_compat_82198() const;
#endif
static void _bind_compatibility_methods();

Comment on lines 831 to 540
#ifndef DISABLE_DEPRECATED
TypedArray<Dictionary> _get_property_list_bind_compat_82198() const;
TypedArray<Dictionary> _get_method_list_bind_compat_82198() const;
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#ifndef DISABLE_DEPRECATED
TypedArray<Dictionary> _get_property_list_bind_compat_82198() const;
TypedArray<Dictionary> _get_method_list_bind_compat_82198() const;
#endif

@AThousandShips
Copy link
Member

AThousandShips commented Feb 7, 2024

Something has broken as there's enums that's been shifted, something in the default generation code must have broken, see CodeEdit

My bad missed that was a direct change, should be added at the end probably though to keep compatibility

@AThousandShips
Copy link
Member

I'd strongly recommend rebasing this and fixing the format so we can get the CI to show some details

@nlupugla nlupugla force-pushed the struct branch 2 times, most recently from 8707fa5 to 154d057 Compare February 7, 2024 16:51
@nlupugla
Copy link
Contributor Author

Hi Folks! I was applying some fixes last week to get this branch to pass the CI and, as you can see, I'm now passing most of the tests and am now dealing with some more obscure errors to me that have less obvious solutions. If anyone more knowledgeable on some of these error messages can help but me on the right track, it would be much appreciated!

  1. Linux / Editor w/ Mono

Am I perhaps doing something wrong with the compatibility methods for core_bind?

Log

Godot.SourceGenerators.Internal -> /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/bin/Debug/netstandard2.0/Godot.SourceGenerators.Internal.dll
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDB.cs(348,48): error CS0111: Type 'ClassDB' already defines a member called 'ClassGetSignal' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDB.cs(359,73): error CS0111: Type 'ClassDB' already defines a member called 'ClassGetSignalList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDB.cs(370,73): error CS0111: Type 'ClassDB' already defines a member called 'ClassGetPropertyList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDB.cs(382,73): error CS0111: Type 'ClassDB' already defines a member called 'ClassGetMethodList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDBInstance.cs(356,41): error CS0111: Type 'ClassDBInstance' already defines a member called 'ClassGetSignal' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDBInstance.cs(367,66): error CS0111: Type 'ClassDBInstance' already defines a member called 'ClassGetSignalList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDBInstance.cs(378,66): error CS0111: Type 'ClassDBInstance' already defines a member called 'ClassGetPropertyList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/ClassDBInstance.cs(390,66): error CS0111: Type 'ClassDBInstance' already defines a member called 'ClassGetMethodList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GodotObject.cs(1056,66): error CS0111: Type 'GodotObject' already defines a member called 'GetPropertyList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GodotObject.cs(1074,66): error CS0111: Type 'GodotObject' already defines a member called 'GetMethodList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GodotObject.cs(1086,66): error CS0111: Type 'GodotObject' already defines a member called 'GetSignalList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GodotObject.cs(1100,66): error CS0111: Type 'GodotObject' already defines a member called 'GetSignalConnectionList' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GodotObject.cs(1114,66): error CS0111: Type 'GodotObject' already defines a member called 'GetIncomingConnections' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GltfAccessor.cs(287,18): warning CS0108: 'GltfAccessor.GetType()' hides inherited member 'object.GetType()'. Use the new keyword if hiding was intended. [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/GltfAccessor.cs(578,43): warning CS0108: 'GltfAccessor.MethodName.GetType' hides inherited member 'object.GetType()'. Use the new keyword if hiding was intended. [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: Process completed with exit code 1.

  1. Linux / Editor with doubles and GCC sanitizers

Is this perhaps triggering because I memnewed a Node in my tests that I didn't free afterwards? https://github.com/godotengine/godot/pull/82198/files#diff-c8898cf9c08702e43ce9ad5f080e9b47ad7e5ad77617c7aa5afd5bba5b7174adR187

Log

Direct leak of 1072 byte(s) in 1 object(s) allocated from:
#0 0x7ff3bebcf808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
#1 0x55a795cb6441 in Memory::alloc_static(unsigned long, bool) (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x52558441)
#2 0x55a795cb6352 in operator new(unsigned long, char const*) (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x52558352)
#3 0x55a779dc3134 (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x36665134)
#4 0x55a77b31b435 in doctest::Context::run() (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x37bbd435)
#5 0x55a77a8f67e5 in test_main(int, char**) (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x371987e5)
#6 0x55a779652487 in Main::test_entrypoint(int, char**, bool&) (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x35ef4487)
#7 0x55a7792c0841 in main (/home/runner/work/godot/godot/bin/godot.linuxbsd.editor.dev.double.x86_64.san+0x35b62841)
#8 0x7ff3bde03082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)

SUMMARY: AddressSanitizer: 4288 byte(s) leaked in 4 allocation(s).
Error: Process completed with exit code 1.

  1. Linux / Editor with clang sanitizers

Same as above?

  1. Linux / Minimal template

Maybe related to a mistake in my compatibility methods as well?

Log

/usr/bin/ld: core/libcore.linuxbsd.template_release.x86_64.a(register_core_types.linuxbsd.template_release.x86_64.o): in function register_core_singletons()': register_core_types.cpp:(.text+0x2668): undefined reference to core_bind::special::ClassDB::_bind_compatibility_methods()'
/usr/bin/ld: register_core_types.cpp:(.text+0x266f): undefined reference to core_bind::special::ClassDB::_bind_compatibility_methods()' /usr/bin/ld: core/libcore.linuxbsd.template_release.x86_64.a(register_core_types.linuxbsd.template_release.x86_64.o): in function core_bind::special::ClassDB::_initialize_classv()':
register_core_types.cpp:(.text._ZN9core_bind7special7ClassDB18_initialize_classvEv[_ZN9core_bind7special7ClassDB18_initialize_classvEv]+0x119): undefined reference to core_bind::special::ClassDB::_bind_compatibility_methods()' /usr/bin/ld: register_core_types.cpp:(.text._ZN9core_bind7special7ClassDB18_initialize_classvEv[_ZN9core_bind7special7ClassDB18_initialize_classvEv]+0x120): undefined reference to core_bind::special::ClassDB::_bind_compatibility_methods()'
scons: building terminated because of errors.
collect2: error: ld returned 1 exit status
scons: *** [bin/godot.linuxbsd.template_release.x86_64] Error 1
[Time elapsed: 00:12:53.456]
Error: Process completed with exit code 2.

@RobProductions
Copy link
Contributor

RobProductions commented Sep 8, 2024

Hey @nlupugla , I pulled down your branch to take a look at it and (correct me if I'm horribly wrong) it seems like the Mono build is failing because the generated C# code found in ClassDB.cs (modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects) did not contain the new methods you added called class_get_signal_as_struct, class_get_signal_list_as_structs, etc. In the Linux CI it seems to duplicate existing methods from some reason (?) or have some other issue, for me on Windows it just doesn't generate them at all. To take a look at why that is, I tried to look into the generated code and I got intellisense working for C# through C# Dev Kit on VSC to track down some stuff; here is an example of an existing function:

public static Godot.Collections.Dictionary ClassGetSignal(StringName @class, StringName signal)
{
    return NativeCalls.godot_icall_2_260(MethodBind8, GodotObject.GetPtr(Singleton), (godot_string_name)(@class?.NativeValue ?? default), (godot_string_name)(signal?.NativeValue ?? default));
}

And I noticed the return type, tracked it down, and found that Collections are manually defined in GodotSharp/Core as a .cs file. Again I may be wrong so I apologize, but it's possible that it can't generate because there is no type Struct<MethodInfo> (used by class_get_signal_as_struct) to use. So one thing to try if you haven't already is to create the Struct in C# within the Godot.Collections namespace, track down if that needs to be added anywhere within the generator, and you can test whether it works by running Godot with --generate-mono-glue in a shell (the recommended process noted in the docs for compiling Godot with C#) and then see if the new functions are added to ClassDB. Hope that helped in at least narrowing the Mono CI issue!

Btw I'm very interested in seeing this merged because it would allow me to implement godotengine/godot-proposals#438 very easily, which would be a huge win for C# users like me :) Would love to collaborate with you on this if you'd like, I'm pretty familiar with C# if you need help implementing the Struct type there!

@nlupugla
Copy link
Contributor Author

nlupugla commented Sep 8, 2024

Hi @RobProductions, thanks for your interest! I would love some help on the C# side. I don't use C# myself so I was pretty well at a loss for how to fix these issues on my own :)

I'll try and rebase this branch today. Then, feel free to tinker around with it and let me know if you manage to come up with a fix.

Feel free to reach out on the Godot contributors chat too: https://chat.godotengine.org/channel/QTu9nG79BRxe8YyqD

@nlupugla nlupugla force-pushed the struct branch 2 times, most recently from f24ca52 to 5e6a92b Compare September 8, 2024 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants