Search code examples
c++godot

Using custom types in Godot with GDExtension


Working on a Godot 4 addon with GDExtension, I find that I cannot compile my source code due to type errors that I don't know exactly how to work around or fix.

I have the following code:

// register_types.cpp
#include "register_types.h"
#include "../godot-cpp/gdextension/gdextension_interface.h"
#include "../godot-cpp/include/godot_cpp/core/class_db.hpp"
#include "../godot-cpp/include/godot_cpp/core/defs.hpp"
#include "../godot-cpp/include/godot_cpp/godot.hpp"

#include "poker.hpp"

void initialize_gdextension_types(godot::ModuleInitializationLevel p_level) {
  if (p_level != godot::MODULE_INITIALIZATION_LEVEL_SCENE) {
    return;
  }
  godot::ClassDB::register_class<Poker>();
  godot::ClassDB::register_class<SingleHandRankingEvaluationResult>();
}

void uninitialize_gdextension_types(godot::ModuleInitializationLevel p_level) {
  if (p_level != godot::MODULE_INITIALIZATION_LEVEL_SCENE) {
    return;
  }
}

extern "C" {
// Initialization
// NOTE: I don't think we need to touch anything here
GDExtensionBool GDE_EXPORT
poker_utils_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address,
                         GDExtensionClassLibraryPtr p_library,
                         GDExtensionInitialization *r_initialization) {
  godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library,
                                                 r_initialization);
  init_obj.register_initializer(initialize_gdextension_types);
  init_obj.register_terminator(uninitialize_gdextension_types);
  init_obj.set_minimum_library_initialization_level(
      godot::MODULE_INITIALIZATION_LEVEL_CORE);

  return init_obj.init();
}
}



// poker.hpp
#include "../godot-cpp/gdextension/gdextension_interface.h"
#include "../godot-cpp/include/godot_cpp/core/class_db.hpp"
#include "../godot-cpp/include/godot_cpp/core/defs.hpp"
#include "../godot-cpp/include/godot_cpp/godot.hpp"
#include "../godot-cpp/include/godot_cpp/templates/vector.hpp"

class Poker : public godot::Object {
  GDCLASS(Poker, godot::Object);

protected:
  static void _bind_methods();

public:
  static godot::PackedStringArray getDeck();
  static godot::PackedStringArray getDeckShuffled();
  static SingleHandRankingEvaluationResult
  singleHandRankEvaluation(const godot::PackedStringArray cards);
};

// ...

struct SingleHandRankingEvaluationResult : public godot::Object {
  GDCLASS(SingleHandRankingEvaluationResult, godot::Object);

private:
  int HandRanking;
  godot::String Error;

protected:
  static void _bind_methods();

public:
  void set_hand_ranking(int ranking);
  int get_hand_ranking();
  void set_error(godot::String error);
  godot::String get_error();
};

// poker.cpp
#include "poker.hpp"
#include "../src/deck.hpp"
#include "../src/hand-evaluation.hpp"
#include <string>
#include <vector>

void SingleHandRankingEvaluationResult::set_hand_ranking(int ranking) {
  this->HandRanking = ranking;
}

int SingleHandRankingEvaluationResult::get_hand_ranking() {
  return this->HandRanking;
}

void SingleHandRankingEvaluationResult::set_error(godot::String error) {
  this->Error = error;
}

godot::String SingleHandRankingEvaluationResult::get_error() {
  return this->Error;
};

void SingleHandRankingEvaluationResult::_bind_methods() {
  godot::ClassDB::bind_method(
      godot::D_METHOD("set_hand_ranking"),
      &SingleHandRankingEvaluationResult::set_hand_ranking);
  godot::ClassDB::bind_method(
      godot::D_METHOD("get_hand_ranking"),
      &SingleHandRankingEvaluationResult::get_hand_ranking);
  godot::ClassDB::bind_method(godot::D_METHOD("set_error"),
                              &SingleHandRankingEvaluationResult::set_error);
  godot::ClassDB::bind_method(godot::D_METHOD("get_error"),
                              &SingleHandRankingEvaluationResult::get_error);

  godot::ClassDB::add_property(
      "SingleHandRankingEvaluationResult",
      godot::PropertyInfo(godot::Variant::INT, "hand_ranking"),
      "set_hand_ranking", "get_hand_ranking");
  godot::ClassDB::add_property(
      "SingleHandRankingEvaluationResult",
      godot::PropertyInfo(godot::Variant::STRING, "error"), "set_error",
      "get_error");
}

SingleHandRankingEvaluationResult
Poker::singleHandRankEvaluation(const godot::PackedStringArray cards) {
  poker::Cards all_cards;

  for (const auto &card : cards) {
    all_cards.push_back(std::string(godot2stdstr(card)));
  }

  auto result = poker::singleHandRankEvaluation(all_cards);
  if (std::holds_alternative<poker::Error>(result)) {
    SingleHandRankingEvaluationResult evaluation;

    evaluation.set_error(std::get<poker::Error>(result).message.c_str());
    return evaluation;
  }

  int ranking = std::get<int>(result);
  SingleHandRankingEvaluationResult evaluation;
  evaluation.set_hand_ranking(ranking);

  return evaluation;
};

when running scons I get these errors:

➜  scons
scons: Reading SConscript files ...
Auto-detected 12 CPU cores available for build parallelism. Using 11 cores by default. You can override it with the -j argument.
Building for architecture x86_64 on platform linux
scons: done reading SConscript files.
scons: Building targets ...
scons: `godot-cpp/bin/libgodot-cpp.linux.template_debug.x86_64.a' is up to date.
g++ -o godot/poker.os -c -std=c++17 -fPIC -Wwrite-strings -m64 -march=x86-64 -O2 -fPIC -DLINUX_ENABLED -DUNIX_ENABLED -DDEBUG_ENABLED -DDEBUG_METHODS_ENABLED -DNDEBUG -Igodot-cpp/gdextension -Igodot-cpp/include -Igodot-cpp/gen/include -I/nix/store/5hch4vqzwan8ksab4pfpyrbb7wvvv3ad-cxxtest-4.4/include -I/nix/store/7nbh3b6hhjqjs3nfy529g38l85iv46i9-openssl-3.0.11-dev/include -Isrc -Iompeval/omp -Igodot godot/poker.cpp
In file included from godot/../godot-cpp/include/godot_cpp/core/class_db.hpp:38,
                 from godot/poker.hpp:5,
                 from godot/poker.cpp:1:
godot-cpp/include/godot_cpp/core/method_bind.hpp: In instantiation of 'GDExtensionVariantType godot::MethodBindTRS<R, P>::gen_argument_type(int) const [with R = SingleHandRankingEvaluationResult; P = {godot::PackedStringArray}]':
godot-cpp/include/godot_cpp/core/method_bind.hpp:674:33:   required from here
godot-cpp/include/godot_cpp/core/method_bind.hpp:678:71: error: incomplete type 'godot::GetTypeInfo<SingleHandRankingEvaluationResult, void>' used in nested name specifier
  678 |                         return GDExtensionVariantType(GetTypeInfo<R>::VARIANT_TYPE);
      |                                                                       ^~~~~~~~~~~~
godot-cpp/include/godot_cpp/core/method_bind.hpp: In instantiation of 'godot::PropertyInfo godot::MethodBindTRS<R, P>::gen_argument_type_info(int) const [with R = SingleHandRankingEvaluationResult; P = {godot::PackedStringArray}]':
godot-cpp/include/godot_cpp/core/method_bind.hpp:682:23:   required from here
godot-cpp/include/godot_cpp/core/method_bind.hpp:688:62: error: incomplete type 'godot::GetTypeInfo<SingleHandRankingEvaluationResult, void>' used in nested name specifier
  688 |                         return GetTypeInfo<R>::get_class_info();
      |                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
godot-cpp/include/godot_cpp/core/method_bind.hpp: In instantiation of 'GDExtensionClassMethodArgumentMetadata godot::MethodBindTRS<R, P>::get_argument_metadata(int) const [with R = SingleHandRankingEvaluationResult; P = {godot::PackedStringArray}]':
godot-cpp/include/godot_cpp/core/method_bind.hpp:697:49:   required from here
godot-cpp/include/godot_cpp/core/method_bind.hpp:701:48: error: incomplete type 'godot::GetTypeInfo<SingleHandRankingEvaluationResult, void>' used in nested name specifier
  701 |                         return GetTypeInfo<R>::METADATA;
      |                                                ^~~~~~~~
In file included from godot-cpp/include/godot_cpp/core/method_bind.hpp:34:
godot-cpp/include/godot_cpp/core/binder_common.hpp: In instantiation of 'void godot::call_with_variant_args_static_ret(R (*)(P ...), const Variant**, Variant&, GDExtensionCallError&, IndexSequence<Is ...>) [with R = SingleHandRankingEvaluationResult; P = {PackedStringArray}; long unsigned int ...Is = {0}]':
godot-cpp/include/godot_cpp/core/binder_common.hpp:584:35:   required from 'void godot::call_with_variant_args_static_ret_dv(R (*)(P ...), const void* const*, int, Variant&, GDExtensionCallError&, const std::vector<Variant>&) [with R = SingleHandRankingEvaluationResult; P = {PackedStringArray}; GDExtensionConstVariantPtr = const void*]'
godot-cpp/include/godot_cpp/core/method_bind.hpp:707:39:   required from 'godot::Variant godot::MethodBindTRS<R, P>::call(GDExtensionClassInstancePtr, const void* const*, GDExtensionInt, GDExtensionCallError&) const [with R = SingleHandRankingEvaluationResult; P = {godot::PackedStringArray}; GDExtensionClassInstancePtr = void*; GDExtensionConstVariantPtr = const void*; GDExtensionInt = long int]'
godot-cpp/include/godot_cpp/core/method_bind.hpp:705:18:   required from here
godot-cpp/include/godot_cpp/core/binder_common.hpp:546:15: error: no match for 'operator=' (operand types are 'godot::Variant' and 'SingleHandRankingEvaluationResult')
  546 |         r_ret = (p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
      |         ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from godot-cpp/include/godot_cpp/core/property_info.hpp:38,
                 from godot-cpp/include/godot_cpp/core/object.hpp:36,
                 from godot-cpp/include/godot_cpp/core/method_ptrcall.hpp:36,
                 from godot-cpp/include/godot_cpp/core/binder_common.hpp:36:
godot-cpp/include/godot_cpp/variant/variant.hpp:252:18: note: candidate: 'godot::Variant& godot::Variant::operator=(const godot::Variant&)'
  252 |         Variant &operator=(const Variant &other);
      |                  ^~~~~~~~
godot-cpp/include/godot_cpp/variant/variant.hpp:252:43: note:   no known conversion for argument 1 from 'SingleHandRankingEvaluationResult' to 'const godot::Variant&'
  252 |         Variant &operator=(const Variant &other);
      |                            ~~~~~~~~~~~~~~~^~~~~
godot-cpp/include/godot_cpp/variant/variant.hpp:253:18: note: candidate: 'godot::Variant& godot::Variant::operator=(godot::Variant&&)'
  253 |         Variant &operator=(Variant &&other);
      |                  ^~~~~~~~
godot-cpp/include/godot_cpp/variant/variant.hpp:253:38: note:   no known conversion for argument 1 from 'SingleHandRankingEvaluationResult' to 'godot::Variant&&'
  253 |         Variant &operator=(Variant &&other);
      |                            ~~~~~~~~~~^~~~~
godot-cpp/include/godot_cpp/core/binder_common.hpp: In instantiation of 'void godot::call_with_ptr_args_static_method_ret_helper(R (*)(P ...), const void* const*, void*, IndexSequence<Is ...>) [with R = SingleHandRankingEvaluationResult; P = {PackedStringArray}; long unsigned int ...Is = {0}; GDExtensionConstTypePtr = const void*]':
godot-cpp/include/godot_cpp/core/binder_common.hpp:594:54:   required from 'void godot::call_with_ptr_args_static_method_ret(R (*)(P ...), const void* const*, void*) [with R = SingleHandRankingEvaluationResult; P = {PackedStringArray}; GDExtensionConstTypePtr = const void*]'
godot-cpp/include/godot_cpp/core/method_bind.hpp:713:39:   required from 'void godot::MethodBindTRS<R, P>::ptrcall(GDExtensionClassInstancePtr, const void* const*, GDExtensionTypePtr) const [with R = SingleHandRankingEvaluationResult; P = {godot::PackedStringArray}; GDExtensionClassInstancePtr = void*; GDExtensionConstTypePtr = const void*; GDExtensionTypePtr = void*]'
godot-cpp/include/godot_cpp/core/method_bind.hpp:711:15:   required from here
godot-cpp/include/godot_cpp/core/binder_common.hpp:589:28: error: 'encode' is not a member of 'godot::PtrToArg<SingleHandRankingEvaluationResult>'
  589 |         PtrToArg<R>::encode(p_method(PtrToArg<P>::convert(p_args[Is])...), r_ret);
      |         ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
scons: *** [godot/poker.os] Error 1
scons: building terminated because of errors.

So I have to ask:

  1. What am I doing wrong that GetTypeInfo is unable to work with my custom type?
  2. What's the easiest way to get my type up and running and be able to compile?
  3. Any alternative to returning errors with GDExtensive?
  4. Is godot::Object the best type to inherit of for your struct? godot-cpp branch 4.1 has no godot::Resource type for some reason.

Solution

  • Your set_error function expects a std::string as parameter but you bound it with ClassDB::bind_method and Godot can't directly convert between std::string and godot::String.

    Whenever you bind methods for Godot's ClassBD you must use Godot API types (so any type available to GDScript). If you need to be able to use std::string consider defining a separate method for that (Not sure if an overload would be handled correctly atm.)

    Edit: To answer your 4th question: Resource still exists but every Godot class has its own header so Resource would be in <godot_cpp/classes/resource.hpp>

    As it turns out the Godot method binder only allows godot::Object pointers as return and parameter type. If a type inherits godot::RefCounted the godot::Ref<T> class can be used as smart pointer that is correctly updated when Godot accesses the object.