Skip to content

Custom type converters for GDExtension #14507

Description

@YakoYakoYokuYoku

Problem or limitation

I have a library with its own vector types and I'd like to convert them to their Godot counterparts so I can use them with the API in an extension. I cannot modify the library to suit Godot, though.

// That's how it looks from a header.

struct Vector {
  float x;
  float y;
  float z;
};

And in a file that implements conversions for the GDExtension.

// I can use these functions to convert types back and forth.

godot::Vector3 vector_to_vector3(const Vector &v) {
  // ...
}

Vector vector_from_vector3(const godot::Vector3 &v) {
  // ...
}

So far so good, but if I have hundreds or thousands of methods I'll have to place many calls of these conversion functions.

// Repeat this as required. You can also use code generation, but the problem is still there.

godot::Vector3 Entity::get_position() const {
  return vector_to_vector3(wrapped.position());
}

void Entity::set_position(const godot::Vector3 &v) {
  wrapped.position() = vector_from_vector3(v);
}

static void Entity::_bind_methods() {
  godot::ClassDB::bind_method(godot::D_METHOD("get_position"), &Entity::get_position);
  godot::ClassDB::bind_method(godot::D_METHOD("set_position", "position"), &Entity::set_position);
}

Proposed improvement

I suggest to add a macro which registers these converters so that they are used under the hood. Godot has a similar thing in form of the PtrToArg template, but that's only for converting a handful of types for function arguments and return values. On the other hand, this registration also lets the programmer to convert between registered types and variants.

// This registers the converters so that they can be used in method bindings, variants, etc.
// We pass both `vector_to_vector3` and `vector_from_vector3` here.

REGISTER_CONVERTER(Vector, godot::Vector3, vector_to_vector3, vector_from_vector3);

// Notice that we use the library types and that the converters are called elsewhere.

Vector Entity::get_position() const {
  return wrapped.position();
}

void Entity::set_position(const Vector &v) {
  wrapped.position() = v;
}

void Entity::_bind_methods() {
  godot::ClassDB::bind_method(godot::D_METHOD("get_position"), &Entity::get_position);
  godot::ClassDB::bind_method(godot::D_METHOD("set_position", "position"), &Entity::set_position);
}

A typical case would be string classes, which there are many of these out there.

REGISTER_CONVERTER(utf::String, godot::String, utf_to_string, utf_from_string);

And not only for builtins, it can also be used to register other types that are wrapped in Godot objects too.

REGISTER_CONVERTER(std::shared_ptr<Sample>, godot::Object, sample_shared_to_object, sample_shared_from_object);
REGISTER_CONVERTER(std::unique_ptr<Sample>, godot::Object, sample_unique_to_object, sample_unique_from_object);

Although this has to be taken with care while taking into consideration that reference to the original Godot object can be lost.

Proposal review

  • Yes, this proposal is highly specific and actionable.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions