Use GDExtensionInstanceBindingCallbacks properly #1

Open
opened 2026-10-21 00:43:53 -07:00 by queertherians.org · 3 comments
Owner

A GDExtensionInstanceBindingCallbacks struct is handed as an argument to the API call gdxx::API::object_set_instance_binding(...), and its meaning is very unclear based on the documentation I have been able to review so far.

Starting at line 65 in include/GodotClass.hpp:

#warning TODO: understand what GDExtensionInstanceBindingCallbacks does and potentially do things here
const GDExtensionInstanceBindingCallbacks class_binding_callbacks =
{
    .create_callback = NULL,
    .free_callback = NULL,
    .reference_callback = NULL,
};

API::object_set_instance_binding(godot_object_handle, gdxx::classlib, this, &class_binding_callbacks);

These callback pointers are left blank in the example documentation. Do we know if these callbacks need to be assigned to anything, and what might need to happen when these functions are called?

Open to comments/suggestions/assistance, either on the BarkVR matrix or in this thread.

A `GDExtensionInstanceBindingCallbacks` struct is handed as an argument to the API call `gdxx::API::object_set_instance_binding(...)`, and its meaning is very unclear based on the documentation I have been able to review so far. Starting at line 65 in [`include/GodotClass.hpp`](https://barkvr.dev/queertherians.org/gdxx/src/commit/188b63a6e5915e28418e8637cabd3aa441de3bd1/include/GodotClass.hpp#L65): ```cpp #warning TODO: understand what GDExtensionInstanceBindingCallbacks does and potentially do things here const GDExtensionInstanceBindingCallbacks class_binding_callbacks = { .create_callback = NULL, .free_callback = NULL, .reference_callback = NULL, }; API::object_set_instance_binding(godot_object_handle, gdxx::classlib, this, &class_binding_callbacks); ``` These callback pointers are left blank [in the example documentation](https://docs.godotengine.org/en/stable/engine_details/engine_api/gdextension/gdextension_c_example.html#a-basic-class:~:text=%2E%2E%2E,-const). Do we know if these callbacks need to be assigned to anything, and what might need to happen when these functions are called? Open to comments/suggestions/assistance, either on the BarkVR matrix or in this thread.
Author
Owner

Did some quick web searching. It seems to be a mechanism that actually helps define how the lifetime of objects is managed across the Godot/Native memory boundary. According to some Google AI slop summaries (urgh) that I've tried to verify with external sources (take it with a grain of salt; I've got no other accessible documentation to work off of, so sadly this is my only route currently), rewritten in my own words:


API::object_get_instance_binding(...) specifically calls .create_callback for an existing copy of an object (for class types that we haven't registered ourselves) IF we do not have a native version of the object already. This allows us to quickly create or fetch a wrapper instance for Godot-initialized objects (gdxx:GodotExternClass? #3). It avoids calling .create_callback and returns a cached reference to a native wrapper if one was already set. Probably does nothing for object_set_instance_binding, since there we're already assigning an instance binding.

.free_callback is called by the engine when .queue_free() on a wrapped object was called. We destruct/free the wrapper, but keep the internal Godot object pointer alive for Godot to free on its own. (We should probably keep this as nullptr for when we're setting the instance binding within ClassCreationInfo.create_instance_func, since we're presumably already deleting the native object in ClassCreationInfo.free_instance_func)

.reference_callback is used for RefCounted (Resource) objects (custom or not), as opposed to objects in the Scene Tree whose lifetime is determined by the Tree. If the provided boolean argument is true, the ref-count is non-zero on the Godot side, so the object shall not be freed (we probably return true). If the provided boolean is false, the Godot-side ref-count (probably?) reached zero, which should exclude Godot from the Native-side ref-count1. If, at that point, the ref-count hits zero on our end too, we return true. Otherwise, we return false, as we still have alive references on our end. We do not do any freeing here, as that is subsequently done in .free_callback (wrapped after make) or ClassCreationInfo.free_instance_func (Native representation only) appropriately if it's called for.


I'm writing all this out here mainly for my own reference, so I can attempt the implementation of this. Despite being figured from AI slop, it seems reasonable based on what I can tell, but I'll still maintain some healthy skepticism. Hopefully upon its successful implementation, this issue should close. This should also solve some of the confusion and future speculation in other issues currently open.


  1. NOT shared_ptr per-se, but a native ref-counted long with a Godot flag. If native ref-count later hits zero with Godot flag false (i.e. outside of .reference_callback), we should probably call for deletion of the object. If Godot flag is true, however, we never free it ourselves, since Godot maintains ownership. ↩︎

Did some quick web searching. It seems to be a mechanism that actually helps define how the lifetime of objects is managed across the Godot/Native memory boundary. According to some Google AI slop summaries (urgh) that I've tried to verify with external sources (take it with a grain of salt; I've got no other accessible documentation to work off of, so sadly this is my only route currently), rewritten in my own words: --- `API::object_get_instance_binding(...)` specifically *calls* `.create_callback` for an *existing* copy of an object (for class types that we haven't registered ourselves) IF we do *not* have a native version of the object already. This allows us to quickly create or fetch a wrapper instance for Godot-initialized objects (`gdxx:GodotExternClass`? #3). It avoids calling `.create_callback` and returns a cached reference to a native wrapper if one was already set. Probably does nothing for `object_set_instance_binding`, since there we're already assigning an instance binding. `.free_callback` is called by the engine when `.queue_free()` on a wrapped object was called. We destruct/free the wrapper, but keep the internal Godot object pointer alive for Godot to free on its own. (We should probably keep this as `nullptr` for when we're setting the instance binding within `ClassCreationInfo.create_instance_func`, since we're presumably already deleting the native object in ClassCreationInfo.free_instance_func) `.reference_callback` is used for RefCounted (Resource) objects (custom or not), as opposed to objects in the Scene Tree whose lifetime is determined by the Tree. If the provided boolean argument is `true`, the ref-count is non-zero on the Godot side, so the object shall not be freed (we probably return `true`). If the provided boolean is `false`, the Godot-side ref-count (probably?) reached zero, which should exclude Godot from the Native-side ref-count[^1]. If, at that point, the ref-count hits zero on our end too, we return `true`. Otherwise, we return `false`, as we still have alive references on our end. We do not do any freeing here, as that is subsequently done in `.free_callback` (wrapped after make) or `ClassCreationInfo.free_instance_func` (Native representation only) appropriately if it's called for. [^1]: *NOT* `shared_ptr` per-se, but a native ref-counted `long` with a Godot flag. If native ref-count later hits zero with Godot flag false (i.e. outside of `.reference_callback`), we should probably call for deletion of the object. If Godot flag is true, however, we never free it ourselves, since Godot maintains ownership. --- I'm writing all this out here mainly for my own reference, so I can attempt the implementation of this. Despite being figured from AI slop, it *seems* reasonable based on what I can tell, but I'll still maintain some healthy skepticism. Hopefully upon its successful implementation, this issue should close. This should also solve some of the confusion and future speculation in other issues currently open.
Author
Owner

Godot Class constructors/destructors cannot be public, because Godot native objects/wrappers need to always initialize/destroy through the proper utility (ClassCreationInfo.create_instance_func or API::object_get_instance_binding(...) -> .create_callback) (always heap allocated; lifetime cannot be restrained to stack-scope).

Static methods for creation and queue_free will be defined for convenience, utilizing the StringNames for the class name (all to be shared within gdxx::GodotClass).

Godot Class constructors/destructors cannot be public, because Godot native objects/wrappers need to always initialize/destroy through the proper utility (`ClassCreationInfo.create_instance_func` or `API::object_get_instance_binding(...)` -> `.create_callback`) (always heap allocated; lifetime cannot be restrained to stack-scope). Static methods for creation and queue_free will be defined for convenience, utilizing the StringNames for the class name (all to be shared within `gdxx::GodotClass`).
Author
Owner

We now depend on ClassDB (to be wrapped in gdxx::GodotExternClass) for detecting whether we are refcounted or not.

We now depend on ClassDB (to be wrapped in `gdxx::GodotExternClass`) for detecting whether we are refcounted or not.
Sign in to join this conversation.
No description provided.