-
-
Notifications
You must be signed in to change notification settings - Fork 21.1k
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
Virtual functions called on objects produced by GDExtension doesn't work on languages other than C++ #63275
Comments
My understanding is that this is what the |
Yes, this is how I would expect it to work. Yes, it gives extension developer a way to supply function pointers to godot, but they still doesn't know what functions they are expected to supply |
Well I've encountered this as a challenge whilst writing a Go extension interface, it would be nice if Ie. like this: extern uint8_t goClassGetVirtual(void *classID, const char *p_name);
extern void goMethodCallDirect(uintptr_t methodIndex, uintptr_t instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret);
void goClassCallVirtual1(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret) {goMethodCallDirect((uintptr_t)1, (uintptr_t)p_instance, p_args, r_ret);}
void goClassCallVirtual2(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret) {goMethodCallDirect((uintptr_t)2, (uintptr_t)p_instance, p_args, r_ret);}
void goClassCallVirtual255(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret) {goMethodCallDirect((uintptr_t)255, (uintptr_t)p_instance, p_args, r_ret);}
GDNativeExtensionClassCallVirtual get_virtual_func(void *p_userdata, const char *p_name) {
switch (goClassGetVirtual(p_userdata, p_name)) {
case 1: return goClassCallVirtual1;
case 2: return goClassCallVirtual2;
...
case 255: return goClassCallVirtual255;
default: return NULL;
}
} Which language are you working with? |
I've also run into the exact same issue in chat a couple weeks back In summary, you cannot call a go method directly from C as only functions can be exported to C. So, you have to workaround this by setting up virtual function callback in a non-idiomatic go way. I was able to implement an example
as opposed to a method "attached" to a go struct.... something like this:
this effectively requires users of the library to be exposed to cgo as part of the library DSL, which is not ideal for the average go developer. the average go developer won't know the intricacies of working in cgo. To get this working, you also have to wrap the callback in C code:
this functions can now be registered in go code when initializing the custom GDClass:
if there was a way to pass a maybe a "virtual function call mode" can be provided when registering the GDExtension with Godot... either to keep the current behavior or add in a |
So, besides the function pointer, you would like to be requested an userdata (void*) too that can be called together with the function called? If this is the case and it makes everything simpler for you, let me know. |
@reduz for my use case, a userdata param would provide the flexibility for me to store a pointer to the go function as the userdata so that i can have a single go function exported to C as a gateway to call into go |
This is also an issue for Dart (where I've used a template metaprogramming aproach to get around it. A similar problem is going to exist for me for Not sure if I should create a separate issue for the binding callbacks? |
I'm thinking about helping with this, but I don't see any way to add it without breaking compatibility, as it's a change to the One possibility would be to add a Thoughts? |
My opinion is that compatability doesn't matter in this case.
|
This is actually exactly the approach that we've discussed for doing this at previous GDExtension meetings. We still need to hash out the naming convention, but I'd personally be for something like I'm actually working on another PR (#80284) that also needs to add a field to the That said, I haven't yet had a chance to actually look at this issue and evaluate it, I'm just responding to the question of how to make this sort of change to |
I can work on a PR based on #80284 if you'd like, once we have an approach. The initial request was to allow a piece of user data to be returned in addition to the function pointer from I have two potential thoughts. First would be to change typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_method, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); I feel like this would deal with my use case fairly easily. I'd always return the same function for The second would be to instead add a function to Personally, I like the first solution better. |
It may take a while for PR #80284 to be finished, so I wouldn't base your work on it. However, PR #78634 is currently in progress of adding the necessary compatibility changes. It's not quite there yet, but once all the review is addressed it should be a good example, and hopefully it won't take too long to get merged.
Hm, I'm not sure about this. In your case the method name might be enough, but what if the particular language binding needs a pointer to some object in the VM (for a VM-based language)? I don't entirely understand the Go case described above (because I'm not very familiar with Go), but it sounds like they may need more than the name too? We don't have a C language binding, but imagining in my head how I'd want a nice C binding to work, I think I'd probably want to use a pointer to the user's C function (with nice argument types) in the user data, and give Godot a pointer to a different C function (defined in the language bindings, not the user code) that can decode those arguments into the right types and call the user's C function.
So, you're imagining that we have a |
In reality both ideas were just about sending in the method name with the call, not any user data. I've had a chance to look over how virtual methods in Godot work, and I'm starting to think that letting extension authors keep track of user data for virtual methods is going to be better, even though it (might) be more work on their part, it would end up saving Godot a lot of memory it might not need to allocate. At least not without a complete re-think on how Godot handles virtual methods. Here's why: Every virtual methods in Godot generate 3 variables in the final class (from StringName _gdvirtual_##m_name##_sn = #m_name;\\
mutable bool _gdvirtual_##m_name##_initialized = false;\\
mutable GDExtensionClassCallVirtual _gdvirtual_##m_name = nullptr;\\ When a virtual method is called, it checks if the virtual is initialized. If it's not, it calls the mutable void* _gdvirtual_##m_name##_user_data = nullptr; And change the signature of My guess is most extension developers can get everything they need just from the TL;DR - Adding user data for virtual methods incurs a 4-8 byte penalty per virtual method per object regardless of whether that object is ever overridden in any extension. That seems excessive to me, but maybe that's a trade-off we're willing to make? |
If we went with the approach that I thought you were suggesting above:
... then we'd only need to keep the one pointer for the user data, since we'd always be calling the I think we could even have that one pointer do double duty for the compatibility mode: if the GDExtension provides the older Anyway, this is probably something that should be discussed at a GDExtension meeting! And, ideally, we should get input from as many language binding authors as we can. |
So after the GDExtension meeting I think I get the need for User Data over just passing the name, but let me see if I have your logic right in pseudo code format:
I worry a little bit about doubling up the use of that pointer, but it does avoid the extra memory allocation. We may also want to drop a warning or something in somewhere that checks that if you bind Does that all look correct? |
Yep, that's basically what I was describing above!
This should definitely all be documented in comments in |
So... I was implementing this and realized that it really should have an extra method: Alternately, we assume / document that pointers returned from I was able to implement this for godot_dart without the free (as the only data I need fits in a pointer) and it works pretty well. |
Opened a draft PR for this anyway, assuming we don't want to add complexity to Object destruction. |
Thanks for starting the PR!
Ah, yeah, if the user data is constructed specifically for the given Object instance then freeing it with the instance would be very tricky. We'd need to do something similar to the tracking being added in PR #80284 for hot-reload, but that's purposefully only enabled when necessary (ie. not in release builds) to avoid the performance hit of the extra accounting. Perhaps we should just say as part of the API that user data shouldn't be constructed for a given Object, but for the class as a whole, and should be freed (if necessary) by the GDExtension when it's being deinitialized? That way this problem isn't handled at all on the Godot side, it's all up to the GDExtension to manage. (That would also fit in pretty nicely with how the hot-reload PR works so far.) This would also mean we don't need to add anything additional to your PR to handle this. :-)
I'm not sure I understand what you mean by this. If you mean that the GDExtension should free the user data when the Or, if you mean that the Godot side will attempt to free the user data when it's calling |
I've got a PR build against your godot changes. I've take the godot-cpp test harness as a template for this repo. I've been able to get Edit: virtual functions are working as expected |
This adds two functions to `GDExtensionClassCreationInfo` that allow for developers to supply a generic virtual call function along with user data to be sent to that call. If `get_virutal_call_data_func` is not null, extensions call this function to get user data to pass to a supplied `call_virtual_with_data_func`. Both must be provided is one is provided. If `get_virtual_call_data_func` is null, Godot falls back to the old `get_virtual_func` logic. Fixes godotengine#63275 Co-authored-by: David Snopek <[email protected]>
Godot version
4.0.alpha12
System information
Windows 10
Issue description
When you register a new class from GDExtension,
class_get_default_property_value
instantiates it through callingcreate_instance_func
passed intoGDNativeExtensionClassCreationInfo
.Then it gets casted to Object:
godot/core/object/class_db.cpp
Line 1429 in a9e4eac
After which 'get_property_list' gets called on this Object. And inside of it there are at least 2 calles to virtual functions. Like this one:
godot/core/object/object.cpp
Line 505 in a9e4eac
This calles would only makes sense if the object has
__vfptr
table, which is C++ specific.Pointof GDExtensions is to allow for other languages to interact with Godot, which this issue limits significantly.
Steps to reproduce
You can return C
struct
fromcreate_instance_func
in your GDExtension and see how Godot crashes because of access violation.I'm not sure how to describe steps to reproduce otherwise.
Minimal reproduction project
No response
The text was updated successfully, but these errors were encountered: