Browse Source

Merge pull request #82264 from dalexeev/core-builtin-methods-as-callables

Core: Allow methods of built-in `Variant` types to be used as Callables
Rémi Verschelde 1 year ago
parent
commit
b5c6e870e8

+ 1 - 0
core/variant/variant.h

@@ -572,6 +572,7 @@ public:
 	static ValidatedBuiltInMethod get_validated_builtin_method(Variant::Type p_type, const StringName &p_method);
 	static ValidatedBuiltInMethod get_validated_builtin_method(Variant::Type p_type, const StringName &p_method);
 	static PTRBuiltInMethod get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method);
 	static PTRBuiltInMethod get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method);
 
 
+	static MethodInfo get_builtin_method_info(Variant::Type p_type, const StringName &p_method);
 	static int get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method);
 	static int get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method);
 	static Variant::Type get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument);
 	static Variant::Type get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument);
 	static String get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument);
 	static String get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument);

+ 48 - 37
core/variant/variant_call.cpp

@@ -1114,6 +1114,46 @@ struct VariantBuiltInMethodInfo {
 	Variant::Type return_type;
 	Variant::Type return_type;
 	int argument_count = 0;
 	int argument_count = 0;
 	Variant::Type (*get_argument_type)(int p_arg) = nullptr;
 	Variant::Type (*get_argument_type)(int p_arg) = nullptr;
+
+	MethodInfo get_method_info(const StringName &p_name) const {
+		MethodInfo mi;
+		mi.name = p_name;
+
+		if (has_return_type) {
+			mi.return_val.type = return_type;
+			if (mi.return_val.type == Variant::NIL) {
+				mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+			}
+		}
+
+		if (is_const) {
+			mi.flags |= METHOD_FLAG_CONST;
+		}
+		if (is_vararg) {
+			mi.flags |= METHOD_FLAG_VARARG;
+		}
+		if (is_static) {
+			mi.flags |= METHOD_FLAG_STATIC;
+		}
+
+		for (int i = 0; i < argument_count; i++) {
+			PropertyInfo pi;
+#ifdef DEBUG_METHODS_ENABLED
+			pi.name = argument_names[i];
+#else
+			pi.name = "arg" + itos(i + 1);
+#endif
+			pi.type = (*get_argument_type)(i);
+			if (pi.type == Variant::NIL) {
+				pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+			}
+			mi.arguments.push_back(pi);
+		}
+
+		mi.default_arguments = default_arguments;
+
+		return mi;
+	}
 };
 };
 
 
 typedef OAHashMap<StringName, VariantBuiltInMethodInfo> BuiltinMethodMap;
 typedef OAHashMap<StringName, VariantBuiltInMethodInfo> BuiltinMethodMap;
@@ -1268,6 +1308,13 @@ Variant::PTRBuiltInMethod Variant::get_ptr_builtin_method(Variant::Type p_type,
 	return method->ptrcall;
 	return method->ptrcall;
 }
 }
 
 
+MethodInfo Variant::get_builtin_method_info(Variant::Type p_type, const StringName &p_method) {
+	ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, MethodInfo());
+	const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+	ERR_FAIL_NULL_V(method, MethodInfo());
+	return method->get_method_info(p_method);
+}
+
 int Variant::get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method) {
 int Variant::get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method) {
 	ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0);
 	ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0);
 	const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
 	const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
@@ -1378,43 +1425,7 @@ void Variant::get_method_list(List<MethodInfo> *p_list) const {
 		for (const StringName &E : builtin_method_names[type]) {
 		for (const StringName &E : builtin_method_names[type]) {
 			const VariantBuiltInMethodInfo *method = builtin_method_info[type].lookup_ptr(E);
 			const VariantBuiltInMethodInfo *method = builtin_method_info[type].lookup_ptr(E);
 			ERR_CONTINUE(!method);
 			ERR_CONTINUE(!method);
-
-			MethodInfo mi;
-			mi.name = E;
-
-			//return type
-			if (method->has_return_type) {
-				mi.return_val.type = method->return_type;
-				if (mi.return_val.type == Variant::NIL) {
-					mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
-				}
-			}
-
-			if (method->is_const) {
-				mi.flags |= METHOD_FLAG_CONST;
-			}
-			if (method->is_vararg) {
-				mi.flags |= METHOD_FLAG_VARARG;
-			}
-			if (method->is_static) {
-				mi.flags |= METHOD_FLAG_STATIC;
-			}
-			for (int i = 0; i < method->argument_count; i++) {
-				PropertyInfo pi;
-#ifdef DEBUG_METHODS_ENABLED
-				pi.name = method->argument_names[i];
-#else
-				pi.name = "arg" + itos(i + 1);
-#endif
-				pi.type = method->get_argument_type(i);
-				if (pi.type == Variant::NIL) {
-					pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
-				}
-				mi.arguments.push_back(pi);
-			}
-
-			mi.default_arguments = method->default_arguments;
-			p_list->push_back(mi);
+			p_list->push_back(method->get_method_info(E));
 		}
 		}
 	}
 	}
 }
 }

+ 81 - 0
core/variant/variant_callable.cpp

@@ -0,0 +1,81 @@
+/**************************************************************************/
+/*  variant_callable.cpp                                                  */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "variant_callable.h"
+
+#include "core/templates/hashfuncs.h"
+
+bool VariantCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+	return p_a->hash() == p_b->hash();
+}
+
+bool VariantCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+	return p_a->hash() < p_b->hash();
+}
+
+uint32_t VariantCallable::hash() const {
+	return h;
+}
+
+String VariantCallable::get_as_text() const {
+	return vformat("%s::%s (Callable)", Variant::get_type_name(variant.get_type()), method);
+}
+
+CallableCustom::CompareEqualFunc VariantCallable::get_compare_equal_func() const {
+	return compare_equal;
+}
+
+CallableCustom::CompareLessFunc VariantCallable::get_compare_less_func() const {
+	return compare_less;
+}
+
+bool VariantCallable::is_valid() const {
+	return Variant::has_builtin_method(variant.get_type(), method);
+}
+
+StringName VariantCallable::get_method() const {
+	return method;
+}
+
+ObjectID VariantCallable::get_object() const {
+	return ObjectID();
+}
+
+void VariantCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+	Variant v = variant;
+	v.callp(method, p_arguments, p_argcount, r_return_value, r_call_error);
+}
+
+VariantCallable::VariantCallable(const Variant &p_variant, const StringName &p_method) {
+	variant = p_variant;
+	method = p_method;
+	h = variant.hash();
+	h = hash_murmur3_one_64(Variant::get_builtin_method_hash(variant.get_type(), method), h);
+}

+ 58 - 0
core/variant/variant_callable.h

@@ -0,0 +1,58 @@
+/**************************************************************************/
+/*  variant_callable.h                                                    */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef VARIANT_CALLABLE_H
+#define VARIANT_CALLABLE_H
+
+#include "core/variant/callable.h"
+#include "core/variant/variant.h"
+
+class VariantCallable : public CallableCustom {
+	Variant variant;
+	StringName method;
+	uint32_t h = 0;
+
+	static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+	static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
+
+public:
+	uint32_t hash() const override;
+	String get_as_text() const override;
+	CompareEqualFunc get_compare_equal_func() const override;
+	CompareLessFunc get_compare_less_func() const override;
+	bool is_valid() const override;
+	StringName get_method() const override;
+	ObjectID get_object() const override;
+	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+	VariantCallable(const Variant &p_variant, const StringName &p_method);
+};
+
+#endif // VARIANT_CALLABLE_H

+ 29 - 24
core/variant/variant_setget.cpp

@@ -30,6 +30,8 @@
 
 
 #include "variant_setget.h"
 #include "variant_setget.h"
 
 
+#include "variant_callable.h"
+
 struct VariantSetterGetterInfo {
 struct VariantSetterGetterInfo {
 	void (*setter)(Variant *base, const Variant *value, bool &valid);
 	void (*setter)(Variant *base, const Variant *value, bool &valid);
 	void (*getter)(const Variant *base, Variant *value);
 	void (*getter)(const Variant *base, Variant *value);
@@ -264,42 +266,45 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool
 }
 }
 
 
 Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
 Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
-	Variant ret;
 	uint32_t s = variant_setters_getters[type].size();
 	uint32_t s = variant_setters_getters[type].size();
 	if (s) {
 	if (s) {
 		for (uint32_t i = 0; i < s; i++) {
 		for (uint32_t i = 0; i < s; i++) {
 			if (variant_setters_getters_names[type][i] == p_member) {
 			if (variant_setters_getters_names[type][i] == p_member) {
+				Variant ret;
 				variant_setters_getters[type][i].getter(this, &ret);
 				variant_setters_getters[type][i].getter(this, &ret);
 				r_valid = true;
 				r_valid = true;
 				return ret;
 				return ret;
 			}
 			}
 		}
 		}
+	}
 
 
-		r_valid = false;
-
-	} else if (type == Variant::OBJECT) {
-		Object *obj = get_validated_object();
-		if (!obj) {
-			r_valid = false;
-			return "Instance base is null.";
-		} else {
-			return obj->get(p_member, &r_valid);
-		}
-	} else if (type == Variant::DICTIONARY) {
-		const Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member);
-		if (v) {
-			r_valid = true;
-
-			return *v;
-		} else {
-			r_valid = false;
-		}
-
-	} else {
-		r_valid = false;
+	switch (type) {
+		case Variant::OBJECT: {
+			Object *obj = get_validated_object();
+			if (!obj) {
+				r_valid = false;
+				return "Instance base is null.";
+			} else {
+				return obj->get(p_member, &r_valid);
+			}
+		} break;
+		case Variant::DICTIONARY: {
+			const Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member);
+			if (v) {
+				r_valid = true;
+				return *v;
+			}
+		} break;
+		default: {
+			if (Variant::has_builtin_method(type, p_member)) {
+				r_valid = true;
+				return Callable(memnew(VariantCallable(*this, p_member)));
+			}
+		} break;
 	}
 	}
 
 
-	return ret;
+	r_valid = false;
+	return Variant();
 }
 }
 
 
 /**** INDEXED SETTERS AND GETTERS ****/
 /**** INDEXED SETTERS AND GETTERS ****/

+ 0 - 11
doc/classes/Callable.xml

@@ -46,17 +46,6 @@
 		    # Prints "Attack!", when the button_pressed signal is emitted.
 		    # Prints "Attack!", when the button_pressed signal is emitted.
 		    button_pressed.connect(func(): print("Attack!"))
 		    button_pressed.connect(func(): print("Attack!"))
 		[/codeblock]
 		[/codeblock]
-		[b]Note:[/b] Methods of native types such as [Signal], [Array], or [Dictionary] are not of type [Callable] in order to avoid unnecessary overhead. If you need to pass those methods as [Callable], use a lambda function as a wrapper.
-		[codeblock]
-		func _init():
-		    var my_dictionary = { "hello": "world" }
-
-		    # This will not work, `clear` is not a callable.
-		    create_tween().tween_callback(my_dictionary.clear)
-
-		    # This will work, as lambdas are custom callables.
-		    create_tween().tween_callback(func(): my_dictionary.clear())
-		[/codeblock]
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 4 - 0
modules/gdscript/gdscript_analyzer.cpp

@@ -3658,6 +3658,10 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 							return;
 							return;
 						}
 						}
 					}
 					}
+					if (Variant::has_builtin_method(base.builtin_type, name)) {
+						p_identifier->set_datatype(make_callable_type(Variant::get_builtin_method_info(base.builtin_type, name)));
+						return;
+					}
 					if (base.is_hard_type()) {
 					if (base.is_hard_type()) {
 #ifdef SUGGEST_GODOT4_RENAMES
 #ifdef SUGGEST_GODOT4_RENAMES
 						String rename_hint = String();
 						String rename_hint = String();

+ 6 - 0
modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd

@@ -0,0 +1,6 @@
+func test():
+	var array: Array = [1, 2, 3]
+	print(array)
+	var callable: Callable = array.clear
+	callable.call()
+	print(array)

+ 3 - 0
modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out

@@ -0,0 +1,3 @@
+GDTEST_OK
+[1, 2, 3]
+[]