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

Allow method binds to take Object subclasses as arguments #57205

Merged
merged 1 commit into from Jan 27, 2022

Conversation

TechnoPorg
Copy link
Contributor

Using some new C++ features, this PR adds a condition to VariantCaster that casts Variants of type OBJECT to any type T, if T is derived from Object.
This change enables a fair bit of code cleanup. First, the Variant implicit cast operators for Node and Control can be removed, which allows for some of the invalid includes mentioned in #53295 to be removed. Second, helper methods in Tree, whose sole purpose was to cast arguments to TreeItem *, are no longer necessary.
A few small changes also had to be made to other files, due to the changes cascading down all the includes.

Fixes #21160, closes #20538, supersedes #57169

@TechnoPorg TechnoPorg requested review from a team as code owners January 25, 2022 15:53
This commit adds a condition to VariantCaster that casts Variants of type OBJECT to any type T, if T is derived from Object.
This change enables a fair bit of code cleanup. First, the Variant implicit cast operators for Node and Control can be removed, which allows for some invalid includes to be removed. Second, helper methods in Tree whose sole purpose was to cast arguments to TreeItem * are no longer necessary.
A few small changes also had to be made to other files, due to the changes cascading down all the includes.
@akien-mga
Copy link
Member

That's pretty cool! There might be quite a few other occurrences of bindings where return types or arguments have been limited to Object *, Node * or Control * when they could be more specific (I'd suggest searching for them with regex on the class reference XML), but it's probably better to wait for the Variant change to be approved and merged before changing further bindings in a follow-up PR.

@Zireael07
Copy link
Contributor

@akien-mga; Just to be clear, you recommend that OP do not change any more bindings in this PR?

@aaronfranke
Copy link
Member

Does this mean that we can bind LineEdit as the type of the method I changed in #48920? Awesome! But yes, it's probably best to get the Variant changes in first before we start changing things around the engine.

@Xrayez
Copy link
Contributor

Xrayez commented Jan 26, 2022

My take on this was #42060, @reduz was not happy with implementation because that would require using VARIANT_OBJECT_CAST(Object) for each class.

So yeah... I confirm this would be useful, even from the user perspective (I'd also remove specific casts from the Goost module in two places (https://github.com/goostengine/goost):

Therefore, this PR supersedes #42060 as well.

@TechnoPorg
Copy link
Contributor Author

TechnoPorg commented Jan 26, 2022

VARIANT_ENUM_CAST could probably even be removed with the same technique, but using std::is_enum. That change should go in a separate PR if it's desired, though.

EDIT: Never mind, there's no way to get the name of an enum in string format, without using a macro.

@reduz
Copy link
Member

reduz commented Jan 27, 2022

Would still be good if @neikeq gives it a review

@akien-mga akien-mga requested a review from neikeq January 27, 2022 08:36
@neikeq
Copy link
Contributor

neikeq commented Jan 27, 2022

Second, helper methods in Tree, whose sole purpose was to cast arguments to TreeItem *, are no longer necessary.

Why were they needed? Is it because of DEFVALs?

@YuriSizov
Copy link
Contributor

Second, helper methods in Tree, whose sole purpose was to cast arguments to TreeItem *, are no longer necessary.

Why were they needed? Is it because of DEFVALs?

Because you could not bind methods with Object derivatives as arguments/return value with ClassDB before this PR. So we use Object everywhere even if there is a way narrower type expected, and then cast and check inside of every method. I guess whoever made Tree just added a wrapper to save a few lines.

@akien-mga
Copy link
Member

Yeah, see https://github.com/godotengine/godot/pull/57205/files#diff-73e583f1bfffebc5e30a925726bf40f32d72daaea940bf229e71479e98f8d1f7R339 - prior to this PR the only types that could be used in bindings were Object *, Control * and Node * because they had dedicated operators implemented in Variant (and this made Variant depend on node.h and control.h).

@neikeq
Copy link
Contributor

neikeq commented Jan 27, 2022

Ah, I see. I never realized we were using only those 3 object types in method signatures.
This looks fine then. Both PtrToArg (for ptrcall) and GetTypeInfo (for bindings generators) already handle this.

Copy link
Member

@akien-mga akien-mga left a comment

Choose a reason for hiding this comment

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

Looks great to me! I'm looking forward to improving the existing bindings with this more flexible system.

@akien-mga
Copy link
Member

akien-mga commented Jan 28, 2022

There might be quite a few other occurrences of bindings where return types or arguments have been limited to Object *, Node * or Control * when they could be more specific (I'd suggest searching for them with regex on the class reference XML)

So I had a look at occurrences for type="Object" in the class reference XML (rg -g'*.xml' 'type="Object"'), here's what I found.

Normal method bindings

Some cases that could be improved easily with the new feature:

Virtual methods

Virtual methods are bound with GDVIRTUAL* macros and those don't seem to support types more specific than Object *. I don't know if this can be improved. Here's an example:

scene/gui/control.h
301:    GDVIRTUAL1RC(Object *, _make_custom_tooltip, String)

(This could return Control * in theory.)

PropertyInfo

I found a few under-specified object types for some signals declared like this:

scene/gui/base_button.cpp
506:    ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button")));

This can be worked around via a stringly typed hint in PropertyInfo like this:

ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button", PROPERTY_HINT_RESOURCE_TYPE, "BaseButton")));

(Even though it's not a Resource type but a Node here.)

This is used throughout the engine for signals and properties to specific their actual type in the bindings.
Might be worth looking into to see if a solution similar to the one used here could make it possible to detect these types properly without having to rely on manually specified strings.

Edit: This seems only needed for signals, for properties the type is inferred from the setter/getter.
Edit 2: For now #57358 fixes the few occurrences where PROPERTY_HINT_RESOURCE_TYPE was not used but could be.

@TechnoPorg TechnoPorg deleted the variant-template-cast branch January 28, 2022 16:25
@Xrayez
Copy link
Contributor

Xrayez commented Feb 6, 2022

This would be nice to implement in 3.x, if that doesn't break compatibility (I need this for custom modules and implementing custom data structures, such as graph: goostengine/goost#172).

@TechnoPorg
Copy link
Contributor Author

TechnoPorg commented Feb 6, 2022

I'm not sure if this can be implemented in 3.x, since if constexpr is a C++ 17 feature, and 3.x uses C++ 14. I couldn't get this to compile with a regular if statement, but maybe that could be worked around by using a constexpr bool first, and then testing that. Or maybe the template wizardry used in GetTypeInfo could work for VariantCaster as well?

@TechnoPorg
Copy link
Contributor Author

TechnoPorg commented Feb 11, 2022

@Xrayez I tried briefly to make this work on 3.x with a constexpr bool, but the compiler unfortunately didn't cooperate. I don't have any major projects with Godot ongoing, so I'd prefer to focus on 4.0 as much as possible. I would be glad to continue to try and get this working on 3.x if you want me to, but it may be most efficient for goost to use your VARIANT_OBJECT_CAST macro instead.

@Xrayez
Copy link
Contributor

Xrayez commented Feb 11, 2022

No worries, since it's possible to workaround, it's not a big deal. Sometimes I do forget to cast custom objects, though (just like VARIANT_ENUM_CAST).

using TStripped = std::remove_pointer_t<T>;
if constexpr (std::is_base_of<Object, TStripped>::value) {
Object *obj = p_variant;
return Object::cast_to<TStripped>(p_variant) || !obj;
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this supposed to be !obj || Object::cast_to<TStripped>(p_variant)? Getting EA_BAD_ACCESS errors at this line

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it is. Object::cast_to<T>() takes in an Object *, so both values will be the result of p_variant->operator Object*() and will be the same. So, if obj is a nullptr, we shouldn't attempt the cast.

Copy link
Member

Choose a reason for hiding this comment

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

Related #84858
Would your change fix the same issue? Maybe it's better solution.

Copy link
Contributor

Choose a reason for hiding this comment

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

No, it doesn't. This should be changed either way because it's semantically incorrect, but it doesn't fix the issues I was having in #86602

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet