Allow method binds to take Object subclasses as arguments

This commit adds a condition to VariantCaster that casts Variants of type OBJECT to any type T, if T is derived from Object.
This change enables a fair bit of code cleanup. First, the Variant implicit cast operators for Node and Control can be removed, which allows for some invalid includes to be removed. Second, helper methods in Tree whose sole purpose was to cast arguments to TreeItem * are no longer necessary.
A few small changes also had to be made to other files, due to the changes cascading down all the includes.
This commit is contained in:
TechnoPorg 2022-01-25 08:37:41 -07:00
parent 2f4d76f068
commit 051ef479c9
15 changed files with 64 additions and 78 deletions

View file

@ -37,6 +37,8 @@
#include "core/templates/safe_refcount.h"
#include "core/templates/self_list.h"
class Node;
#define RES_BASE_EXTENSION(m_ext) \
public: \
static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension(m_ext, get_class_static()); } \

View file

@ -44,24 +44,42 @@
#include <stdio.h>
// Variant cannot define an implicit cast operator for every Object subclass, so the
// casting is done here, to allow binding methods with parameters more specific than Object *
template <class T>
struct VariantCaster {
static _FORCE_INLINE_ T cast(const Variant &p_variant) {
return p_variant;
using TStripped = std::remove_pointer_t<T>;
if constexpr (std::is_base_of<Object, TStripped>::value) {
return Object::cast_to<TStripped>(p_variant);
} else {
return p_variant;
}
}
};
template <class T>
struct VariantCaster<T &> {
static _FORCE_INLINE_ T cast(const Variant &p_variant) {
return p_variant;
using TStripped = std::remove_pointer_t<T>;
if constexpr (std::is_base_of<Object, TStripped>::value) {
return Object::cast_to<TStripped>(p_variant);
} else {
return p_variant;
}
}
};
template <class T>
struct VariantCaster<const T &> {
static _FORCE_INLINE_ T cast(const Variant &p_variant) {
return p_variant;
using TStripped = std::remove_pointer_t<T>;
if constexpr (std::is_base_of<Object, TStripped>::value) {
return Object::cast_to<TStripped>(p_variant);
} else {
return p_variant;
}
}
};
@ -135,7 +153,13 @@ struct PtrToArg<char32_t> {
template <typename T>
struct VariantObjectClassChecker {
static _FORCE_INLINE_ bool check(const Variant &p_variant) {
return true;
using TStripped = std::remove_pointer_t<T>;
if constexpr (std::is_base_of<Object, TStripped>::value) {
Object *obj = p_variant;
return Object::cast_to<TStripped>(p_variant) || !obj;
} else {
return true;
}
}
};
@ -151,24 +175,6 @@ struct VariantObjectClassChecker<const Ref<T> &> {
}
};
template <>
struct VariantObjectClassChecker<Node *> {
static _FORCE_INLINE_ bool check(const Variant &p_variant) {
Object *obj = p_variant;
Node *node = p_variant;
return node || !obj;
}
};
template <>
struct VariantObjectClassChecker<Control *> {
static _FORCE_INLINE_ bool check(const Variant &p_variant) {
Object *obj = p_variant;
Control *control = p_variant;
return control || !obj;
}
};
#ifdef DEBUG_METHODS_ENABLED
template <class T>

View file

@ -38,8 +38,6 @@
#include "core/math/math_funcs.h"
#include "core/string/print_string.h"
#include "core/variant/variant_parser.h"
#include "scene/gui/control.h"
#include "scene/main/node.h"
String Variant::get_type_name(Variant::Type p_type) {
switch (p_type) {
@ -2006,22 +2004,6 @@ Object *Variant::get_validated_object() const {
}
}
Variant::operator Node *() const {
if (type == OBJECT) {
return Object::cast_to<Node>(_get_obj().obj);
} else {
return nullptr;
}
}
Variant::operator Control *() const {
if (type == OBJECT) {
return Object::cast_to<Control>(_get_obj().obj);
} else {
return nullptr;
}
}
Variant::operator Dictionary() const {
if (type == DICTIONARY) {
return *reinterpret_cast<const Dictionary *>(_data._mem);
@ -3416,7 +3398,7 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method,
}
String class_name = p_base->get_class();
Ref<Script> script = p_base->get_script();
Ref<Resource> script = p_base->get_script();
if (script.is_valid() && script->get_path().is_resource_file()) {
class_name += "(" + script->get_path().get_file() + ")";
}

View file

@ -53,8 +53,6 @@
#include "core/variant/dictionary.h"
class Object;
class Node; // helper
class Control; // helper
struct PropertyInfo;
struct MethodInfo;
@ -339,8 +337,6 @@ public:
operator ::RID() const;
operator Object *() const;
operator Node *() const;
operator Control *() const;
operator Callable() const;
operator Signal() const;

View file

@ -50,7 +50,7 @@
</method>
<method name="create_item">
<return type="TreeItem" />
<argument index="0" name="parent" type="Object" default="null" />
<argument index="0" name="parent" type="TreeItem" default="null" />
<argument index="1" name="idx" type="int" default="-1" />
<description>
Creates an item in the tree and adds it as a child of [code]parent[/code].
@ -170,7 +170,7 @@
</method>
<method name="get_item_area_rect" qualifiers="const">
<return type="Rect2" />
<argument index="0" name="item" type="Object" />
<argument index="0" name="item" type="TreeItem" />
<argument index="1" name="column" type="int" default="-1" />
<description>
Returns the rectangle area for the specified item. If [code]column[/code] is specified, only get the position and size of that column, otherwise get the rectangle containing all columns.
@ -185,7 +185,7 @@
</method>
<method name="get_next_selected">
<return type="TreeItem" />
<argument index="0" name="from" type="Object" />
<argument index="0" name="from" type="TreeItem" />
<description>
Returns the next selected item after the given one, or [code]null[/code] if the end is reached.
If [code]from[/code] is [code]null[/code], this returns the first selected item.
@ -239,7 +239,7 @@
</method>
<method name="scroll_to_item">
<return type="void" />
<argument index="0" name="item" type="Object" />
<argument index="0" name="item" type="TreeItem" />
<description>
Causes the [Tree] to jump to the specified item.
</description>

View file

@ -2695,7 +2695,7 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) {
}
if (!base_node && get_edited_object()->has_method("get_root_path")) {
base_node = get_edited_object()->call("get_root_path");
base_node = Object::cast_to<Node>(get_edited_object()->call("get_root_path"));
}
if (!base_node && Object::cast_to<RefCounted>(get_edited_object())) {

View file

@ -3977,7 +3977,7 @@ void CanvasItemEditor::_selection_changed() {
void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {
Array selection = editor_selection->get_selected_nodes();
if (selection.size() != 1 || (Node *)selection[0] != p_canvas_item) {
if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) {
drag_type = DRAG_NONE;
// Clear the selection

View file

@ -6623,7 +6623,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() {
// For snapping to be performed, there must be solid geometry under at least one of the selected nodes.
// We need to check this before snapping to register the undo/redo action only if needed.
for (int i = 0; i < keys.size(); i++) {
Node *node = keys[i];
Node *node = Object::cast_to<Node>(keys[i]);
Node3D *sp = Object::cast_to<Node3D>(node);
Dictionary d = snap_data[node];
Vector3 from = d["from"];
@ -6645,7 +6645,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() {
// Perform snapping if at least one node can be snapped
for (int i = 0; i < keys.size(); i++) {
Node *node = keys[i];
Node *node = Object::cast_to<Node>(keys[i]);
Node3D *sp = Object::cast_to<Node3D>(node);
Dictionary d = snap_data[node];
Vector3 from = d["from"];

View file

@ -2802,7 +2802,7 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data
}
if (String(d["type"]) == "script_list_element") {
Node *node = d["script_list_element"];
Node *node = Object::cast_to<Node>(d["script_list_element"]);
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);
if (se) {
@ -2875,7 +2875,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co
}
if (String(d["type"]) == "script_list_element") {
Node *node = d["script_list_element"];
Node *node = Object::cast_to<Node>(d["script_list_element"]);
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);
EditorHelp *eh = Object::cast_to<EditorHelp>(node);

View file

@ -362,7 +362,7 @@ void RenameDialog::_post_popup() {
Array selected_node_list = editor_selection->get_selected_nodes();
ERR_FAIL_COND(selected_node_list.size() == 0);
preview_node = selected_node_list[0];
preview_node = Object::cast_to<Node>(selected_node_list[0]);
_update_preview();
_update_substitute();

View file

@ -585,7 +585,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S
stack.push_back(owner_scene_node);
while (!stack.is_empty()) {
current = stack.pop_back();
current = Object::cast_to<Node>(stack.pop_back());
Ref<GDScript> script = current->get_script();
if (script.is_valid() && script->get_path() == path) {
break;

View file

@ -37,6 +37,7 @@
#include "core/variant/array.h"
class Script;
class Node;
class SceneDebugger {
public:

View file

@ -4752,7 +4752,7 @@ bool Tree::get_allow_reselect() const {
void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &Tree::clear);
ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::create_item, DEFVAL(Variant()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_root"), &Tree::get_root);
ClassDB::bind_method(D_METHOD("set_column_custom_minimum_width", "column", "min_width"), &Tree::set_column_custom_minimum_width);
@ -4767,7 +4767,7 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_hide_root", "enable"), &Tree::set_hide_root);
ClassDB::bind_method(D_METHOD("is_root_hidden"), &Tree::is_root_hidden);
ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::_get_next_selected);
ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::get_next_selected);
ClassDB::bind_method(D_METHOD("get_selected"), &Tree::get_selected);
ClassDB::bind_method(D_METHOD("get_selected_column"), &Tree::get_selected_column);
ClassDB::bind_method(D_METHOD("get_pressed_button"), &Tree::get_pressed_button);
@ -4781,7 +4781,7 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column);
ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected);
ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect);
ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::_get_item_rect, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::get_item_rect, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position);
ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position);
ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position);
@ -4805,7 +4805,7 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_column_title_language", "column"), &Tree::get_column_title_language);
ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll);
ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::_scroll_to_item);
ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::scroll_to_item);
ClassDB::bind_method(D_METHOD("set_h_scroll_enabled", "h_scroll"), &Tree::set_h_scroll_enabled);
ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &Tree::is_h_scroll_enabled);

View file

@ -609,23 +609,6 @@ private:
protected:
static void _bind_methods();
//bind helpers
TreeItem *_create_item(Object *p_parent, int p_idx = -1) {
return create_item(Object::cast_to<TreeItem>(p_parent), p_idx);
}
TreeItem *_get_next_selected(Object *p_item) {
return get_next_selected(Object::cast_to<TreeItem>(p_item));
}
Rect2 _get_item_rect(Object *p_item, int p_column) const {
return get_item_rect(Object::cast_to<TreeItem>(p_item), p_column);
}
void _scroll_to_item(Object *p_item) {
scroll_to_item(Object::cast_to<TreeItem>(p_item));
}
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;

View file

@ -51,9 +51,15 @@ public:
TEST_METHODRC,
TEST_METHODRC_ARGS,
TEST_METHOD_DEFARGS,
TEST_METHOD_OBJECT_CAST,
TEST_MAX
};
class ObjectSubclass : public Object {
public:
int value = 1;
};
int test_num = 0;
bool test_valid[TEST_MAX];
@ -98,6 +104,10 @@ public:
test_valid[TEST_METHOD_DEFARGS] = p_arg1 == 1 && p_arg2 == 2 && p_arg3 == 3 && p_arg4 == 4 && p_arg5 == 5; //temporary
}
void test_method_object_cast(ObjectSubclass *p_object) {
test_valid[TEST_METHOD_OBJECT_CAST] = p_object->value == 1;
}
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("test_method"), &MethodBindTester::test_method);
ClassDB::bind_method(D_METHOD("test_method_args"), &MethodBindTester::test_method_args);
@ -108,6 +118,7 @@ public:
ClassDB::bind_method(D_METHOD("test_methodrc"), &MethodBindTester::test_methodrc);
ClassDB::bind_method(D_METHOD("test_methodrc_args"), &MethodBindTester::test_methodrc_args);
ClassDB::bind_method(D_METHOD("test_method_default_args"), &MethodBindTester::test_method_default_args, DEFVAL(9) /* wrong on purpose */, DEFVAL(4), DEFVAL(5));
ClassDB::bind_method(D_METHOD("test_method_object_cast", "object"), &MethodBindTester::test_method_object_cast);
}
virtual void run_tests() {
@ -134,6 +145,10 @@ public:
test_valid[TEST_METHODRC_ARGS] = int(call("test_methodrc_args", test_num)) == test_num && test_valid[TEST_METHODRC_ARGS];
call("test_method_default_args", 1, 2, 3, 4);
ObjectSubclass *obj = memnew(ObjectSubclass);
call("test_method_object_cast", obj);
memdelete(obj);
}
};
@ -152,6 +167,7 @@ TEST_CASE("[MethodBind] check all method binds") {
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODRC]);
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODRC_ARGS]);
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_DEFARGS]);
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_OBJECT_CAST]);
memdelete(mbt);
}