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 vararg methods of builtin classes. #1091

Conversation

Daylily-Zeleen
Copy link
Contributor

@Daylily-Zeleen Daylily-Zeleen commented Apr 14, 2023

Implement builtin classes' vararg methods.

Need #76047.

Now, call(), call_deferred(), rpc(), rpc_id(), bind() in Callable, and Signal::emit() can be called with any count of arguments.

Fixes #802
Fixes #1093

@Daylily-Zeleen Daylily-Zeleen requested a review from a team as a code owner April 14, 2023 08:06
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/implement_builtin_classes_vararg_methods branch 4 times, most recently from a05f1c7 to 964b15a Compare April 14, 2023 08:16
@Daylily-Zeleen Daylily-Zeleen marked this pull request as draft April 14, 2023 08:19
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/implement_builtin_classes_vararg_methods branch from 964b15a to 72cf9bb Compare April 14, 2023 08:31
@Daylily-Zeleen Daylily-Zeleen marked this pull request as ready for review April 14, 2023 08:34
@Calinou Calinou added enhancement This is an enhancement on the current functionality topic:gdextension This relates to the new Godot 4 extension implementation labels Apr 14, 2023
@dsnopek
Copy link
Collaborator

dsnopek commented Jun 9, 2023

Thanks!

I tested this (using Callable::bind()) and it worked! Attached to this comment is a patch that adds an automated test for it. Feel free to add it to your PR (after rebasing, since your PR is from before the tests), otherwise I'll make a follow up later.

The one thing I'm unsure about is putting these implementations into the builtin_vararg_methods.hpp header. Why not put them in the header of the class they belong to? So, for example, putting Callable::bind() in callable.hpp?

gdextension-callable-bind-test.txt

@dsnopek
Copy link
Collaborator

dsnopek commented Jun 16, 2023

Discussed at the GDExtension meeting, and agreed that the generated functions would be better in the *.hpp files associated with the class that the methods are on.

@Daylily-Zeleen
Copy link
Contributor Author

Discussed at the GDExtension meeting, and agreed that the generated functions would be better in the *.hpp files associated with the class that the methods are on.

I had tried to put implements in theirs *.hpp file, it will occur some compile issue.
If I recall correctly, if I put implements in theirs *.hpp file, they need include variant.hpp.
However, variant.hpp already include these builtin classes through including builtin_types.hpp.

@dsnopek
Copy link
Collaborator

dsnopek commented Jun 19, 2023

Hm. How does Godot itself workaround this problem? The same issue should exist there, because variant.h includes callable.h. callable.h is forward declaring Variant, which I actually had thought would only work if all references are Variant * or Variant &, but Callable::callv() returns Variant, and the vararg template functions seem to look very similar to the ones generated in this PR.

@Daylily-Zeleen
Copy link
Contributor Author

Daylily-Zeleen commented Jun 21, 2023

Hm. How does Godot itself workaround this problem?

I roughly skimmed the implement of Callable::bind() in godot source code.
It is declared in callable.h, but is implemented in variant.h.
For other methods, they are not templates in godot.

So, I think this pr is reasonable.

@dsnopek
Copy link
Collaborator

dsnopek commented Jun 30, 2023

I just attempted to modify this PR to put the template implementations in the same header file as the their classes (so, callable.hpp and signal.hpp). It was able to get it to work for all methods except for Callable::call() because it returns a Variant, and I'm not aware of a way to workaround the problem that return types can't be forward declared.

Looking at the Godot code, it doesn't have this problem, because it doesn't have a Callable::call() method, only Callable::callv() and Callable::callp() which aren't templates (so completely defining Variant can wait until later).

I'm not sure on the best way forward. This PR does include the new header it creates at the end of variant.hpp, so if a developer includes variant.hpp then they're good. But I'm worried about developers that include only callable.hpp or signal.hpp, then try to call any of the vararg methods and it won't be obvious to them that they need to include the builtin_vararg_methods.hpp header.

Maybe we could include the definition of all the methods except for Callable::call() in their respective header files? That way the problem only comes up for Callable::call(), but not Signal::emit(), for example?

Probably worth discussing again at a GDExtension meeting!

@mihe
Copy link
Contributor

mihe commented Jun 30, 2023

Probably worth pointing out that variant.hpp is included by object.hpp (and maybe some even "more core" header file) and as such it would likely be difficult to find yourself in a situation where you're including callable.hpp/signal.hpp without already having variant.hpp included.

@dsnopek
Copy link
Collaborator

dsnopek commented Jun 30, 2023

That's a good point! In a GDExtension, you are very, very likely to be declaring a child class of Object and so you would have included object.hpp.

So, perhaps this PR is fine as-is?

@@ -342,6 +343,101 @@ def generate_builtin_bindings(api, output_dir, build_config):

builtin_binds_file.write("\n".join(builtin_binds))

# Craete a header to implement all builin class vararg methods and be included in `variant.hpp``.
Copy link
Contributor

Choose a reason for hiding this comment

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

Some minor typos here, like Craete, builin and a redundant backtick at the end.

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 your warning.

@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/implement_builtin_classes_vararg_methods branch 2 times, most recently from e27ec9b to 165dc52 Compare July 3, 2023 03:34
@Daylily-Zeleen
Copy link
Contributor Author

Daylily-Zeleen commented Jul 3, 2023

@dsnopek , @mihe
I think in some single files, user still have possibility of including callable.hpp without using any thing of Object, for example, to create a series of utility functions.

If user include variant.hpp, everything is fine.
But if user include callable.hpp only, for example:

// callable_test.h

#pragma once

namespace godot {
class Callable;
}

class Utilities {
public:
	static void bind_extra_int(godot::Callable p_callable);
};
//  callable_test.cpp

#include "callable_test.h"

#include <godot_cpp/variant/callable.hpp>

void Utilities::bind_extra_int(godot::Callable p_callable) {
    p_callable.bind(1);
}

It wiil occur a compile error "LNK2019" about godot::Callable::bind<int>.

So, we still need to deal this problem.

@Daylily-Zeleen
Copy link
Contributor Author

Daylily-Zeleen commented Jul 3, 2023

I found that I have omitted a template in "callable.h" in godot sourc code:

// callable.h
	template <typename... VarArgs>
	void call_deferred(VarArgs... p_args) const {
		Variant args[sizeof...(p_args) + 1] = { p_args..., 0 }; // +1 makes sure zero sized arrays are also supported.
		const Variant *argptrs[sizeof...(p_args) + 1];
		for (uint32_t i = 0; i < sizeof...(p_args); i++) {
			argptrs[i] = &args[i];
		}
		return call_deferredp(sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
	}

Dose it means that if user want to call Callable::call_deferred, they should include "variant.h" instead of "callable.h" in godot modulde?

I written a simple test for this in godot module:

// callable_test.h
#pragma once

class Callable;

class TestUtilities {
public:
	static void callable_call_defferred(Callable &p_callable);
};
// callable_test.cpp
#include "callable_test.h"

// Everything is fine.
#include "core/variant/variant.h"

// Compile error!!
// #include "core/variant/callable.h"

void TestUtilities::callable_call_defferred(Callable &p_callable){
    p_callable.call_deferred("Test");
}

As the code comment show, if I include "callable.h" only and call Callable::call_deferred with argument. it will occur compile errors about "'args' using undefined class 'Variant'".

The conclusion is that, if we want to using these templates, we must include "variant.h"/"variant.hpp" both in GDExtension and godot module.

But there has a problem confuse me: why Callable::call_deferred() define in "callable.h", but Callable::bind() define in "variant.h"?
And where should we define these templates? In their "*.hpp" or in "variant.hpp" like current implement in this pr?

@mihe
Copy link
Contributor

mihe commented Jul 3, 2023

why Callable::call_deferred() define in "callable.h", but Callable::bind() define in "variant.h"?

That is indeed a bit strange, but seeing as how you will likely run into compile errors with either of the two solutions (if you fail to include variant.h) then I guess there is no correct way of doing it in this case.

Clearly there's some precedence for this circular dependency in Godot as well, as you point out, so I would put my vote on just going with the pull request as-is. The case for utility functions creating problems seems niche enough to not be a major concern.

@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/implement_builtin_classes_vararg_methods branch 2 times, most recently from 74b751f to c3418a9 Compare July 3, 2023 09:20
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/implement_builtin_classes_vararg_methods branch from c3418a9 to 3ce83c1 Compare July 3, 2023 09:22
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/implement_builtin_classes_vararg_methods branch from 3ce83c1 to 3536803 Compare July 3, 2023 09:30
@dsnopek
Copy link
Collaborator

dsnopek commented Jul 11, 2023

There's a PR to add a vararg Callable::call() in the engine: godotengine/godot#79341

Looking at the CI, it appears to be encountering the same problem with Variant being an incomplete type that we encountered here. :-) Let's see how this gets resolved upstream, and maybe we can mimick that

@Daylily-Zeleen
Copy link
Contributor Author

Daylily-Zeleen commented Jul 12, 2023

There's a PR to add a vararg Callable::call() in the engine: godotengine/godot#79341

Looking at the CI, it appears to be encountering the same problem with Variant being an incomplete type that we encountered here. :-) Let's see how this gets resolved upstream, and maybe we can mimick that

image

In godotengine/godot#79341, Callable::call() is defined in "variant.h" like Callable::bind(), it has the same result like this pr.

I think if we keep the "variant.hpp" include "callable.hpp", we have not a perfect solution to use Callable's vararg templates by including "callable.hpp" only.

What about to generate a comment line before these templates to warn users? like this:

	// Please including "variant.hpp" instead of including "callable.hpp" only if you need to call this function.
	template <class... Args>
	Callable bind(const Args &...args) const;

If user encounter this compile issue and go to function definitions, they can get the solution by this comment.

@dsnopek
Copy link
Collaborator

dsnopek commented Jul 13, 2023

We could add a comment, however, I'm not sure we're really encouraging users of godot-cpp to read the generated source code.

I've personally come around to liking this PR as-is. IMO, if we see users having issues with this, we can re-evaluate then.

But that's just my opinion! We have this PR on the agenda for discussion at an upcoming GDExtension meeting.

@dsnopek
Copy link
Collaborator

dsnopek commented Jul 14, 2023

Discussed at GDExtension meeting, and we think this fine - templates are tricky, and it looks like core is going to solve the problem in a similar way.

@dsnopek
Copy link
Collaborator

dsnopek commented Jul 14, 2023

I had forgotten that this on depends on Godot PR godotengine/godot#76047

I've just re-reviewed that one (which is looking great!) but we'll have to wait for it to be merged before this one can be

@dsnopek
Copy link
Collaborator

dsnopek commented Sep 2, 2023

PR godotengine/godot#76047 has been merged, so we can finally merge this one :-)

@dsnopek dsnopek merged commit 8990d5a into godotengine:master Sep 2, 2023
@Daylily-Zeleen Daylily-Zeleen deleted the daylily-zeleen/implement_builtin_classes_vararg_methods branch September 3, 2023 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement This is an enhancement on the current functionality topic:gdextension This relates to the new Godot 4 extension implementation
Projects
None yet
4 participants