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 CallableCustom objects to be created from GDExtensions #79005

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions core/extension/gdextension_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,152 @@
#include "core/variant/variant.h"
#include "core/version.h"

class CallableCustomExtension : public CallableCustom {
void *userdata;
void *token;

ObjectID object;

GDExtensionCallableCustomCall call_func;
GDExtensionCallableCustomIsValid is_valid_func;
GDExtensionCallableCustomFree free_func;

GDExtensionCallableCustomEqual equal_func;
GDExtensionCallableCustomLessThan less_than_func;

GDExtensionCallableCustomToString to_string_func;

uint32_t _hash;

static bool default_compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
const CallableCustomExtension *a = static_cast<const CallableCustomExtension *>(p_a);
const CallableCustomExtension *b = static_cast<const CallableCustomExtension *>(p_b);

if (a->call_func != b->call_func || a->userdata != b->userdata) {
return false;
}
return true;
}

static bool default_compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
const CallableCustomExtension *a = static_cast<const CallableCustomExtension *>(p_a);
const CallableCustomExtension *b = static_cast<const CallableCustomExtension *>(p_b);

if (a->call_func != b->call_func) {
return a->call_func < b->call_func;
}
return a->userdata < b->userdata;
}

static bool custom_compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
const CallableCustomExtension *a = static_cast<const CallableCustomExtension *>(p_a);
const CallableCustomExtension *b = static_cast<const CallableCustomExtension *>(p_b);

if (a->equal_func != b->equal_func) {
return false;
}
return a->equal_func(a->userdata, b->userdata);
}

static bool custom_compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
const CallableCustomExtension *a = static_cast<const CallableCustomExtension *>(p_a);
const CallableCustomExtension *b = static_cast<const CallableCustomExtension *>(p_b);

if (a->less_than_func != b->less_than_func) {
return default_compare_less(p_a, p_b);
}
return a->less_than_func(a->userdata, b->userdata);
}

public:
uint32_t hash() const override {
return _hash;
}

String get_as_text() const override {
if (to_string_func != nullptr) {
String out;
GDExtensionBool is_valid = false;

to_string_func(userdata, &is_valid, (GDExtensionStringPtr)&out);

if (is_valid) {
return out;
}
}
return "<CallableCustom>";
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it makes sense to have this say <CallableCustomExtension> instead?

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'm not sure the distinction between CallableCustomExtension used for GDExtension and CallableCustom used by Godot makes much difference here, if more clarity is needed it would be better to provide a to_string_func with any needed details about the callable.

}

CompareEqualFunc get_compare_equal_func() const override {
return (equal_func != nullptr) ? custom_compare_equal : default_compare_equal;
}

CompareLessFunc get_compare_less_func() const override {
return (less_than_func != nullptr) ? custom_compare_less : default_compare_less;
}

bool is_valid() const override {
if (is_valid_func != nullptr && !is_valid_func(userdata)) {
return false;
}
return call_func != nullptr;
}

StringName get_method() const override {
return StringName();
}

ObjectID get_object() const override {
return object;
}

void *get_userdata(void *p_token) const {
return (p_token == token) ? userdata : nullptr;
}

void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
GDExtensionCallError error;

call_func(userdata, (GDExtensionConstVariantPtr *)p_arguments, p_argcount, (GDExtensionVariantPtr)&r_return_value, &error);

r_call_error.error = (Callable::CallError::Error)error.error;
r_call_error.argument = error.argument;
r_call_error.expected = error.expected;
}

CallableCustomExtension(GDExtensionCallableCustomInfo *p_info) {
userdata = p_info->callable_userdata;
token = p_info->token;

if (p_info->object != nullptr) {
object = ((Object *)p_info->object)->get_instance_id();
}

call_func = p_info->call_func;
is_valid_func = p_info->is_valid_func;
free_func = p_info->free_func;

equal_func = p_info->equal_func;
less_than_func = p_info->less_than_func;

to_string_func = p_info->to_string_func;

// Pre-calculate the hash.
if (p_info->hash_func != nullptr) {
_hash = p_info->hash_func(userdata);
} else {
_hash = hash_murmur3_one_64((uint64_t)call_func);
_hash = hash_murmur3_one_64((uint64_t)userdata, _hash);
}
}

~CallableCustomExtension() {
if (free_func != nullptr) {
free_func(userdata);
}
}
};

// Core interface functions.
GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name) {
return GDExtension::get_interface_function(p_name);
Expand Down Expand Up @@ -1138,6 +1284,22 @@ static GDExtensionScriptInstancePtr gdextension_object_get_script_instance(GDExt
return script_instance_extension->instance;
}

static void gdextension_callable_custom_create(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo *p_custom_callable_info) {
memnew_placement(r_callable, Callable(memnew(CallableCustomExtension(p_custom_callable_info))));
}

static void *gdextension_callable_custom_get_userdata(GDExtensionTypePtr p_callable, void *p_token) {
const Callable &callable = *reinterpret_cast<const Callable *>(p_callable);
if (!callable.is_custom()) {
return nullptr;
}
const CallableCustomExtension *custom_callable = dynamic_cast<const CallableCustomExtension *>(callable.get_custom());
if (!custom_callable) {
return nullptr;
}
return custom_callable->get_userdata(p_token);
}

static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash) {
const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
const StringName methodname = *reinterpret_cast<const StringName *>(p_methodname);
Expand Down Expand Up @@ -1312,6 +1474,8 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(placeholder_script_instance_create);
REGISTER_INTERFACE_FUNC(placeholder_script_instance_update);
REGISTER_INTERFACE_FUNC(object_get_script_instance);
REGISTER_INTERFACE_FUNC(callable_custom_create);
REGISTER_INTERFACE_FUNC(callable_custom_get_userdata);
REGISTER_INTERFACE_FUNC(classdb_construct_object);
REGISTER_INTERFACE_FUNC(classdb_get_method_bind);
REGISTER_INTERFACE_FUNC(classdb_get_class_tag);
Expand Down
69 changes: 69 additions & 0 deletions core/extension/gdextension_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,47 @@ typedef struct {
GDExtensionVariantPtr *default_arguments;
} GDExtensionClassMethodInfo;

typedef void (*GDExtensionCallableCustomCall)(void *callable_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
typedef GDExtensionBool (*GDExtensionCallableCustomIsValid)(void *callable_userdata);
typedef void (*GDExtensionCallableCustomFree)(void *callable_userdata);

typedef uint32_t (*GDExtensionCallableCustomHash)(void *callable_userdata);
typedef GDExtensionBool (*GDExtensionCallableCustomEqual)(void *callable_userdata_a, void *callable_userdata_b);
typedef GDExtensionBool (*GDExtensionCallableCustomLessThan)(void *callable_userdata_a, void *callable_userdata_b);

typedef void (*GDExtensionCallableCustomToString)(void *callable_userdata, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out);

typedef struct {
/* Only `call_func` and `token` are strictly required, however, `object` should be passed if its not a static method.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: it's

*
* `token` should point to an address that uniquely identifies the GDExtension (for example, the
* `GDExtensionClassLibraryPtr` passed to the entry symbol function.
*
* `hash_func`, `equal_func`, and `less_than_func` are optional. If not provided both `call_func` and
* `callable_userdata` together are used as the identity of the callable for hashing and comparison purposes.
*
* The hash returned by `hash_func` is cached, `hash_func` will not be called more than once per callable.
*
* `is_valid_func` is necessary if the validity of the callable can change before destruction.
*
* `free_func` is necessary if `callable_userdata` needs to be cleaned up when the callable is freed.
*/
void *callable_userdata;
void *token;

GDExtensionObjectPtr object;

GDExtensionCallableCustomCall call_func;
GDExtensionCallableCustomIsValid is_valid_func;
GDExtensionCallableCustomFree free_func;

GDExtensionCallableCustomHash hash_func;
dsnopek marked this conversation as resolved.
Show resolved Hide resolved
GDExtensionCallableCustomEqual equal_func;
GDExtensionCallableCustomLessThan less_than_func;

GDExtensionCallableCustomToString to_string_func;
} GDExtensionCallableCustomInfo;

/* SCRIPT INSTANCE EXTENSION */

typedef void *GDExtensionScriptInstanceDataPtr; // Pointer to custom ScriptInstance native implementation.
Expand Down Expand Up @@ -2245,6 +2286,34 @@ typedef void (*GDExtensionInterfacePlaceHolderScriptInstanceUpdate)(GDExtensionS
*/
typedef GDExtensionScriptInstanceDataPtr (*GDExtensionInterfaceObjectGetScriptInstance)(GDExtensionConstObjectPtr p_object, GDExtensionObjectPtr p_language);

/* INTERFACE: Callable */

/**
* @name callable_custom_create
* @since 4.2
*
* Creates a custom Callable object from a function pointer.
*
* Provided struct can be safely freed once the function returns.
*
* @param r_callable A pointer that will receive the new Callable.
* @param p_callable_custom_info The info required to construct a Callable.
*/
typedef void (*GDExtensionInterfaceCallableCustomCreate)(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo *p_callable_custom_info);

/**
* @name callable_custom_get_userdata
* @since 4.2
*
* Retrieves the userdata pointer from a custom Callable.
*
* If the Callable is not a custom Callable or the token does not match the one provided to callable_custom_create() via GDExtensionCallableCustomInfo then NULL will be returned.
*
* @param p_callable A pointer to a Callable.
* @param p_token A pointer to an address that uniquely identifies the GDExtension.
*/
typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstTypePtr p_callable, void *p_token);

/* INTERFACE: ClassDB */

/**
Expand Down
Loading