Auto-bind virtual method overrides

pull/602/head
George Marques 2021-08-19 20:03:11 -03:00 committed by Bastiaan Olij
parent b3a4a2cf93
commit a0634cca3f
7 changed files with 224 additions and 273 deletions

View File

@ -562,23 +562,8 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
# Done in the header because of the template.
continue
method_signature = ""
if "return_type" in method:
method_signature += f'{correct_type(method["return_type"])} '
else:
method_signature += "void "
method_signature += f'{class_name}::{method["name"]}('
if "arguments" in method:
method_signature += make_function_parameters(
method["arguments"], include_default=False, for_builtin=True
)
method_signature += ")"
if method["is_const"]:
method_signature += " const"
method_signature += " {"
result.append(method_signature)
method_signature = make_signature(class_name, method, for_builtin=True)
result.append(method_signature + "{")
method_call = "\t"
if "return_type" in method:
@ -758,6 +743,12 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
if len(fully_used_classes) > 0:
result.append("")
if class_name != "Object":
result.append("#include <godot_cpp/core/class_db.hpp>")
result.append("")
result.append("#include <type_traits>")
result.append("")
result.append("namespace godot {")
result.append("")
@ -783,114 +774,52 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append("\t};")
result.append("")
has_vararg_method = False
if "methods" in class_api:
for method in class_api["methods"]:
if method["is_virtual"]:
# TODO: See how to bind virtual methods (if they are even needed).
# Will be done later.
continue
method_signature = "\t"
vararg = "is_vararg" in method and method["is_vararg"]
method_signature = "\t"
if vararg:
has_vararg_method = True
method_signature += "private: "
if "return_value" in method:
method_signature += correct_type(
method["return_value"]["type"],
method["return_value"]["meta"] if "meta" in method["return_value"] else None,
)
else:
method_signature += "void"
if not method_signature.endswith("*"):
method_signature += " "
method_signature += f'{escape_identifier(method["name"])}'
if vararg or (use_template_get_node and class_name == "Node" and method["name"] == "get_node"):
method_signature += "_internal"
method_signature += "("
method_arguments = []
if "arguments" in method:
method_arguments = method["arguments"]
if not vararg:
method_signature += make_function_parameters(
method_arguments, include_default=True, is_vararg=vararg, for_builtin=False
)
else:
method_signature += "const Variant **args, GDNativeInt arg_count"
method_signature += ")"
if method["is_const"]:
method_signature += " const"
method_signature += ";"
result.append(method_signature)
method_signature += make_signature(
class_name, method, for_header=True, use_template_get_node=use_template_get_node
)
result.append(method_signature + ";")
if vararg:
# Add templated version.
method_signature = "\tpublic: template<class... Args> "
result += make_varargs_template(method)
if "return_value" in method:
method_signature += correct_type(
method["return_value"]["type"],
method["return_value"]["meta"] if "meta" in method["return_value"] else None,
)
else:
method_signature += "void"
# Virtuals now.
for method in class_api["methods"]:
if not method["is_virtual"]:
continue
if not method_signature.endswith("*"):
method_signature += " "
method_signature = "\t"
method_signature += make_signature(class_name, method, for_header=True, use_template_get_node=use_template_get_node)
result.append(method_signature + ";")
method_signature += f'{escape_identifier(method["name"])}'
method_arguments = []
if "arguments" in method:
method_arguments = method["arguments"]
method_signature += "("
method_signature += make_function_parameters(method_arguments, include_default=True, is_vararg=vararg)
method_signature += ")"
if method["is_const"]:
method_signature += " const"
method_signature += " {"
result.append(method_signature)
args_array = f"\t\tstd::array<Variant, {len(method_arguments)} + sizeof...(Args)> variant_args {{ "
for argument in method_arguments:
if argument["type"] == "Variant":
args_array += argument["name"]
else:
args_array += f'Variant({argument["name"]})'
args_array += ", "
args_array += "Variant(args)... };"
result.append(args_array)
result.append(f"\t\tstd::array<const Variant *, {len(method_arguments)} + sizeof...(Args)> call_args;")
result.append("\t\tfor(size_t i = 0; i < variant_args.size(); i++) {")
result.append("\t\t\tcall_args[i] = &variant_args[i];")
result.append("protected:")
result.append("\ttemplate <class T>")
result.append("\tstatic void register_virtuals() {")
if class_name != "Object":
result.append(f"\t\t{inherits}::register_virtuals<T>();")
if "methods" in class_api:
for method in class_api["methods"]:
if not method["is_virtual"]:
continue
method_name = escape_identifier(method["name"])
result.append(f"\t\tif constexpr (!std::is_same_v<decltype(&{class_name}::{method_name}),decltype(&T::{method_name})>) {{")
result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});")
result.append("\t\t}")
call_line = "\t\t"
if "return_value" in method and method["return_value"]["type"] != "void":
call_line += "return "
call_line += f'{escape_identifier(method["name"])}_internal(call_args.data(), variant_args.size());'
result.append(call_line)
result.append("\t}")
result.append("\t}")
result.append("")
result.append("public:")
# Special cases.
if class_name == "Object":
@ -899,7 +828,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append("\tstatic T *cast_to(Object *p_object);")
elif use_template_get_node and class_name == "Node":
result.append("\ttemplate<class T>")
result.append("\tT *get_node(const NodePath &p_path) { return Object::cast_to<T>(get_node_internal(p_path)); }")
result.append("\tT *get_node(const NodePath &p_path) const { return Object::cast_to<T>(get_node_internal(p_path)); }")
# Constructor.
result.append("")
@ -943,49 +872,14 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
if "methods" in class_api:
for method in class_api["methods"]:
if method["is_virtual"]:
# TODO: See how to bind virtual methods (if they are even needed).
# Will be done later
continue
vararg = "is_vararg" in method and method["is_vararg"]
# Method signature.
method_signature = ""
if "return_value" in method:
method_signature += correct_type(
method["return_value"]["type"],
method["return_value"]["meta"] if "meta" in method["return_value"] else None,
)
else:
method_signature += "void"
if not method_signature.endswith("*"):
method_signature += " "
method_signature += f'{class_name}::{escape_identifier(method["name"])}'
if vararg or (use_template_get_node and class_name == "Node" and method["name"] == "get_node"):
method_signature += "_internal"
method_signature += "("
method_arguments = []
if "arguments" in method:
method_arguments = method["arguments"]
if not vararg:
method_signature += make_function_parameters(
method_arguments, include_default=False, is_vararg=vararg, for_builtin=False
)
else:
method_signature += "const Variant **args, GDNativeInt arg_count"
method_signature += ")"
if method["is_const"]:
method_signature += " const"
method_signature += " {"
result.append(method_signature)
method_signature = make_signature(class_name, method, use_template_get_node=use_template_get_node)
result.append(method_signature + " {")
# Method body.
result.append(
@ -1044,6 +938,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
result.append("}")
result.append("")
# Virtuals now.
for method in class_api["methods"]:
if not method["is_virtual"]:
continue
method_signature = make_signature(class_name, method, use_template_get_node=use_template_get_node)
method_signature += " {"
if "return_value" in method and correct_type(method["return_value"]["type"]) != "void":
result.append(method_signature)
result.append(f'\treturn {get_default_value_for_type(method["return_value"]["type"])};')
result.append("}")
else:
method_signature += "}"
result.append(method_signature)
result.append("")
# Constructor.
result.append(f"{class_name}::{class_name}() : {inherits}(godot::internal::empty_constructor()) {{")
result.append(
@ -1138,85 +1048,12 @@ def generate_utility_functions(api, output_dir):
vararg = "is_vararg" in function and function["is_vararg"]
function_signature = "\t"
if vararg:
function_signature += "private: "
function_signature += "static "
if "return_type" in function:
if not vararg:
function_signature += f'{correct_type(function["return_type"])} '
else:
function_signature += "Variant "
else:
function_signature += "void "
function_signature += f'{escape_identifier(function["name"])}'
if vararg:
function_signature += "_internal"
function_signature += "("
function_arguments = []
if "arguments" in function:
function_arguments = function["arguments"]
if not vararg:
function_signature += make_function_parameters(function_arguments, include_default=False)
else:
function_signature += "const Variant **args, GDNativeInt arg_count"
function_signature += ");"
header.append(function_signature)
function_signature += make_signature("UtilityFunctions", function, for_header=True, static=True)
header.append(function_signature + ";")
if vararg:
# Add templated version.
method_signature = "\tpublic: template<class... Args> static "
if "return_type" in function:
method_signature += correct_type(function["return_type"])
else:
method_signature += "void"
if not method_signature.endswith("*"):
method_signature += " "
method_signature += f'{escape_identifier(function["name"])}'
method_arguments = []
if "arguments" in function:
method_arguments = function["arguments"]
method_signature += "("
method_signature += make_function_parameters(method_arguments, include_default=True, is_vararg=vararg)
method_signature += ")"
method_signature += " {"
header.append(method_signature)
args_array = f"\t\tstd::array<Variant, {len(method_arguments)} + sizeof...(Args)> variant_args {{ "
for argument in method_arguments:
if argument["type"] == "Variant":
args_array += argument["name"]
else:
args_array += f'Variant({argument["name"]})'
args_array += ", "
args_array += "Variant(args)... };"
header.append(args_array)
header.append(f"\t\tstd::array<const Variant *, {len(method_arguments)} + sizeof...(Args)> call_args;")
header.append("\t\tfor(size_t i = 0; i < variant_args.size(); i++) {")
header.append("\t\t\tcall_args[i] = &variant_args[i];")
header.append("\t\t}")
call_line = "\t\t"
if "return_type" in function and function["return_type"] != "void":
call_line += "return "
call_line += f'{escape_identifier(function["name"])}_internal(call_args.data(), variant_args.size());'
header.append(call_line)
header.append("\t}")
header += make_varargs_template(function, static=True)
header.append("};")
header.append("")
@ -1244,34 +1081,8 @@ def generate_utility_functions(api, output_dir):
for function in api["utility_functions"]:
vararg = "is_vararg" in function and function["is_vararg"]
function_signature = ""
if "return_type" in function:
if not vararg:
function_signature += f'{correct_type(function["return_type"])}'
else:
function_signature += "Variant"
else:
function_signature += "void"
if not function_signature.endswith("*"):
function_signature += " "
function_signature += f'UtilityFunctions::{escape_identifier(function["name"])}'
if vararg:
function_signature += "_internal"
function_signature += "("
function_arguments = []
if "arguments" in function:
function_arguments = function["arguments"]
if not vararg:
function_signature += make_function_parameters(function_arguments, include_default=False)
else:
function_signature += "const Variant **args, GDNativeInt arg_count"
function_signature += ") {"
source.append(function_signature)
function_signature = make_signature("UtilityFunctions", function)
source.append(function_signature + " {")
# Function body.
@ -1340,14 +1151,20 @@ def camel_to_snake(name):
def make_function_parameters(parameters, include_default=False, for_builtin=False, is_vararg=False):
signature = []
for par in parameters:
for index, par in enumerate(parameters):
parameter = type_for_parameter(par["type"], par["meta"] if "meta" in par else None)
parameter += escape_identifier(par["name"])
parameter_name = escape_identifier(par["name"])
if len(parameter_name) == 0:
parameter_name = "arg_" + str(index + 1)
parameter += parameter_name
if include_default and "default_value" in par and (not for_builtin or par["type"] != "Variant"):
parameter += " = "
if is_enum(par["type"]):
parameter += f'({correct_type(par["type"])})'
parameter_type = correct_type(par["type"])
if parameter_type == "void":
parameter_type = "Variant"
parameter += f'({parameter_type})'
parameter += correct_default_value(par["default_value"], par["type"])
signature.append(parameter)
@ -1358,7 +1175,9 @@ def make_function_parameters(parameters, include_default=False, for_builtin=Fals
def type_for_parameter(type_name, meta=None):
if is_pod_type(type_name) and type_name != "Nil" or is_enum(type_name):
if type_name == "void":
return "Variant "
elif is_pod_type(type_name) and type_name != "Nil" or is_enum(type_name):
return f"{correct_type(type_name, meta)} "
elif is_variant(type_name) or is_refcounted(type_name):
return f"const {correct_type(type_name)} &"
@ -1395,6 +1214,135 @@ def get_encoded_arg(arg_name, type_name, type_meta):
return (result, name)
def make_signature(class_name, function_data, for_header=False, use_template_get_node=True, for_builtin=False, static=False):
function_signature = ""
is_vararg = "is_vararg" in function_data and function_data["is_vararg"]
if for_header:
if "is_virtual" in function_data and function_data["is_virtual"]:
function_signature += "virtual "
if is_vararg:
function_signature += "private: "
if static:
function_signature += "static "
return_type = "void"
return_meta = None
if "return_type" in function_data:
return_type = correct_type(function_data["return_type"])
elif "return_value" in function_data:
return_type = function_data["return_value"]["type"]
return_meta = function_data["return_value"]["meta"] if "meta" in function_data["return_value"] else None
function_signature += correct_type(
return_type,
return_meta,
)
if not function_signature.endswith("*"):
function_signature += " "
if not for_header:
function_signature += f"{class_name}::"
function_signature += escape_identifier(function_data["name"])
if is_vararg or (
not for_builtin and use_template_get_node and class_name == "Node" and function_data["name"] == "get_node"
):
function_signature += "_internal"
function_signature += "("
arguments = function_data["arguments"] if "arguments" in function_data else []
if not is_vararg:
function_signature += make_function_parameters(arguments, for_header, for_builtin, is_vararg)
else:
function_signature += "const Variant **args, GDNativeInt arg_count"
function_signature += ")"
if "is_const" in function_data and function_data["is_const"]:
function_signature += " const"
return function_signature
def make_varargs_template(function_data, static=False):
result = []
function_signature = "\tpublic: template<class... Args> "
if static:
function_signature += "static "
return_type = "void"
return_meta = None
if "return_type" in function_data:
return_type = correct_type(function_data["return_type"])
elif "return_value" in function_data:
return_type = function_data["return_value"]["type"]
return_meta = function_data["return_value"]["meta"] if "meta" in function_data["return_value"] else None
function_signature += correct_type(
return_type,
return_meta,
)
if not function_signature.endswith("*"):
function_signature += " "
function_signature += f'{escape_identifier(function_data["name"])}'
method_arguments = []
if "arguments" in function_data:
method_arguments = function_data["arguments"]
function_signature += "("
is_vararg = "is_vararg" in function_data and function_data["is_vararg"]
function_signature += make_function_parameters(method_arguments, include_default=True, is_vararg=is_vararg)
function_signature += ")"
if "is_const" in function_data and function_data["is_const"]:
function_signature += " const"
function_signature += " {"
result.append(function_signature)
args_array = f"\t\tstd::array<Variant, {len(method_arguments)} + sizeof...(Args)> variant_args {{ "
for argument in method_arguments:
if argument["type"] == "Variant":
args_array += argument["name"]
else:
args_array += f'Variant({argument["name"]})'
args_array += ", "
args_array += "Variant(args)... };"
result.append(args_array)
result.append(f"\t\tstd::array<const Variant *, {len(method_arguments)} + sizeof...(Args)> call_args;")
result.append("\t\tfor(size_t i = 0; i < variant_args.size(); i++) {")
result.append("\t\t\tcall_args[i] = &variant_args[i];")
result.append("\t\t}")
call_line = "\t\t"
if "return_value" in function_data and function_data["return_value"]["type"] != "void":
call_line += "return "
call_line += f'{escape_identifier(function_data["name"])}_internal(call_args.data(), variant_args.size());'
result.append(call_line)
result.append("\t}")
return result
# Engine idiosyncrasies.

View File

@ -76,6 +76,11 @@ protected:
\
m_class(godot::internal::empty_constructor empty) : m_inherits(empty) {} \
\
template <class T> \
static void register_virtuals() { \
m_inherits::register_virtuals<T>(); \
} \
\
public: \
static void initialize_class() { \
static bool initialized = false; \
@ -85,6 +90,7 @@ public:
m_inherits::initialize_class(); \
if (m_class::_get_bind_methods() != m_inherits::_get_bind_methods()) { \
_bind_methods(); \
m_inherits::register_virtuals<m_class>(); \
} \
initialized = true; \
} \

View File

@ -168,12 +168,12 @@ void call_with_ptr_args_retc_helper(T *p_instance, R (T::*p_method)(P...) const,
}
template <class T, class... P>
void call_with_ptr_args(T *p_instance, void (T::*p_method)(P...), const GDNativeTypePtr *p_args) {
void call_with_ptr_args(T *p_instance, void (T::*p_method)(P...), const GDNativeTypePtr *p_args, void * /*ret*/) {
call_with_ptr_args_helper<T, P...>(p_instance, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
template <class T, class... P>
void call_with_ptr_args(T *p_instance, void (T::*p_method)(P...) const, const GDNativeTypePtr *p_args) {
void call_with_ptr_args(T *p_instance, void (T::*p_method)(P...) const, const GDNativeTypePtr *p_args, void * /*ret*/) {
call_with_ptr_argsc_helper<T, P...>(p_instance, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}

View File

@ -123,12 +123,12 @@ public:
#define BIND_ENUM_CONSTANT(m_constant) \
ClassDB::bind_integer_constant(get_class_static(), __constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant);
#define BIND_VIRTUAL_METHOD(m_method) \
#define BIND_VIRTUAL_METHOD(m_class, m_method) \
{ \
auto ___call##m_method = [](GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr p_ret) -> void { \
call_with_ptr_args(reinterpret_cast<SelfType *>(p_instance), &SelfType::m_method, p_args, p_ret); \
call_with_ptr_args(reinterpret_cast<m_class *>(p_instance), &m_class::m_method, p_args, p_ret); \
}; \
ClassDB::bind_virtual_method(get_class_static(), #m_method, ___call##m_method); \
ClassDB::bind_virtual_method(m_class::get_class_static(), #m_method, ___call##m_method); \
}
template <class T>

View File

@ -260,9 +260,9 @@ public:
}
virtual void ptrcall(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret) const {
#ifdef TYPED_METHOD_BIND
call_with_ptr_args<T, P...>(static_cast<T *>(p_instance), method, p_args);
call_with_ptr_args<T, P...>(static_cast<T *>(p_instance), method, p_args, nullptr);
#else
call_with_ptr_args<MB_T, P...>(reinterpret_cast<MB_T *>(p_instance), method, p_args);
call_with_ptr_args<MB_T, P...>(reinterpret_cast<MB_T *>(p_instance), method, p_args, nullptr);
#endif // TYPED_METHOD_BIND
}
@ -338,9 +338,9 @@ public:
}
virtual void ptrcall(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret) const {
#ifdef TYPED_METHOD_BIND
call_with_ptr_args<T, P...>(static_cast<T *>(p_instance), method, p_args);
call_with_ptr_args<T, P...>(static_cast<T *>(p_instance), method, p_args, nullptr);
#else
call_with_ptr_args<MB_T, P...>(reinterpret_cast<MB_T *>(p_instance), method, p_args);
call_with_ptr_args<MB_T, P...>(reinterpret_cast<MB_T *>(p_instance), method, p_args, nullptr);
#endif // TYPED_METHOD_BIND
}

View File

@ -36,9 +36,6 @@ void Example::_bind_methods() {
BIND_ENUM_CONSTANT(ANSWER_TO_EVERYTHING);
BIND_CONSTANT(CONSTANT_WITHOUT_ENUM);
// Virtual function override.
BIND_VIRTUAL_METHOD(_has_point);
}
// Methods.
@ -83,7 +80,7 @@ Vector2 Example::get_custom_position() const {
}
// Virtual function override.
bool Example::_has_point(const Vector2 &point) {
bool Example::_has_point(const Vector2 &point) const {
Label *label = get_node<Label>("Label");
label->set_text("Got point: " + Variant(point).stringify());

View File

@ -29,7 +29,7 @@ public:
CONSTANT_WITHOUT_ENUM = 314,
};
// Functions
// Functions.
void simple_func();
void simple_const_func() const;
String return_something(const String &base);
@ -37,12 +37,12 @@ public:
Variant varargs_func(const Variant **args, GDNativeInt arg_count, GDNativeCallError &error);
void emit_custom_signal(const String &name, int value);
// Property
// Property.
void set_custom_position(const Vector2 &pos);
Vector2 get_custom_position() const;
// Virtual function override
virtual bool _has_point(const Vector2 &point);
// Virtual function override (no need to bind manually).
virtual bool _has_point(const Vector2 &point) const override;
};
VARIANT_ENUM_CAST(Example, Constants);