Browse Source

Core: Add `Callable.create` static method for `Variant` callables

Danil Alexeev 1 year ago
parent
commit
d90c9db27f

+ 23 - 9
core/variant/callable.cpp

@@ -30,10 +30,11 @@
 
 
 #include "callable.h"
 #include "callable.h"
 
 
-#include "callable_bind.h"
 #include "core/object/object.h"
 #include "core/object/object.h"
 #include "core/object/ref_counted.h"
 #include "core/object/ref_counted.h"
 #include "core/object/script_language.h"
 #include "core/object/script_language.h"
+#include "core/variant/callable_bind.h"
+#include "core/variant/variant_callable.h"
 
 
 void Callable::call_deferredp(const Variant **p_arguments, int p_argcount) const {
 void Callable::call_deferredp(const Variant **p_arguments, int p_argcount) const {
 	MessageQueue::get_singleton()->push_callablep(*this, p_arguments, p_argcount, true);
 	MessageQueue::get_singleton()->push_callablep(*this, p_arguments, p_argcount, true);
@@ -327,14 +328,27 @@ Callable::operator String() const {
 	}
 	}
 }
 }
 
 
+Callable Callable::create(const Variant &p_variant, const StringName &p_method) {
+	ERR_FAIL_COND_V_MSG(p_method == StringName(), Callable(), "Method argument to Callable::create method must be a non-empty string.");
+
+	switch (p_variant.get_type()) {
+		case Variant::NIL:
+			return Callable(ObjectID(), p_method);
+		case Variant::OBJECT:
+			return Callable(p_variant.operator ObjectID(), p_method);
+		default:
+			return Callable(memnew(VariantCallable(p_variant, p_method)));
+	}
+}
+
 Callable::Callable(const Object *p_object, const StringName &p_method) {
 Callable::Callable(const Object *p_object, const StringName &p_method) {
-	if (p_method == StringName()) {
+	if (unlikely(p_method == StringName())) {
 		object = 0;
 		object = 0;
-		ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string");
+		ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string.");
 	}
 	}
-	if (p_object == nullptr) {
+	if (unlikely(p_object == nullptr)) {
 		object = 0;
 		object = 0;
-		ERR_FAIL_MSG("Object argument to Callable constructor must be non-null");
+		ERR_FAIL_MSG("Object argument to Callable constructor must be non-null.");
 	}
 	}
 
 
 	object = p_object->get_instance_id();
 	object = p_object->get_instance_id();
@@ -342,9 +356,9 @@ Callable::Callable(const Object *p_object, const StringName &p_method) {
 }
 }
 
 
 Callable::Callable(ObjectID p_object, const StringName &p_method) {
 Callable::Callable(ObjectID p_object, const StringName &p_method) {
-	if (p_method == StringName()) {
+	if (unlikely(p_method == StringName())) {
 		object = 0;
 		object = 0;
-		ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string");
+		ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string.");
 	}
 	}
 
 
 	object = p_object;
 	object = p_object;
@@ -352,9 +366,9 @@ Callable::Callable(ObjectID p_object, const StringName &p_method) {
 }
 }
 
 
 Callable::Callable(CallableCustom *p_custom) {
 Callable::Callable(CallableCustom *p_custom) {
-	if (p_custom->referenced) {
+	if (unlikely(p_custom->referenced)) {
 		object = 0;
 		object = 0;
-		ERR_FAIL_MSG("Callable custom is already referenced");
+		ERR_FAIL_MSG("Callable custom is already referenced.");
 	}
 	}
 	p_custom->referenced = true;
 	p_custom->referenced = true;
 	object = 0; //ensure object is all zero, since pointer may be 32 bits
 	object = 0; //ensure object is all zero, since pointer may be 32 bits

+ 2 - 0
core/variant/callable.h

@@ -125,6 +125,8 @@ public:
 
 
 	operator String() const;
 	operator String() const;
 
 
+	static Callable create(const Variant &p_variant, const StringName &p_method);
+
 	Callable(const Object *p_object, const StringName &p_method);
 	Callable(const Object *p_object, const StringName &p_method);
 	Callable(ObjectID p_object, const StringName &p_method);
 	Callable(ObjectID p_object, const StringName &p_method);
 	Callable(CallableCustom *p_custom);
 	Callable(CallableCustom *p_custom);

+ 1 - 0
core/variant/variant_call.cpp

@@ -2038,6 +2038,7 @@ static void _register_variant_builtin_methods() {
 
 
 	/* Callable */
 	/* Callable */
 
 
+	bind_static_method(Callable, create, sarray("variant", "method"), varray());
 	bind_method(Callable, callv, sarray("arguments"), varray());
 	bind_method(Callable, callv, sarray("arguments"), varray());
 	bind_method(Callable, is_null, sarray(), varray());
 	bind_method(Callable, is_null, sarray(), varray());
 	bind_method(Callable, is_custom, sarray(), varray());
 	bind_method(Callable, is_custom, sarray(), varray());

+ 32 - 2
doc/classes/Callable.xml

@@ -4,7 +4,7 @@
 		A built-in type representing a method or a standalone function.
 		A built-in type representing a method or a standalone function.
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
-		[Callable] is a built-in [Variant] type that represents a function. It can either be a method within an [Object] instance, or a standalone function not related to any object, like a lambda function. Like all [Variant] types, it can be stored in variables and passed to other functions. It is most commonly used for signal callbacks.
+		[Callable] is a built-in [Variant] type that represents a function. It can either be a method within an [Object] instance, or a custom callable used for different purposes (see [method is_custom]). Like all [Variant] types, it can be stored in variables and passed to other functions. It is most commonly used for signal callbacks.
 		[b]Example:[/b]
 		[b]Example:[/b]
 		[codeblocks]
 		[codeblocks]
 		[gdscript]
 		[gdscript]
@@ -46,6 +46,22 @@
 		    # 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]
+		In GDScript, you can access methods and global functions as [Callable]s:
+		[codeblock]
+		tween.tween_callback(node.queue_free)  # Object methods.
+		tween.tween_callback(array.clear)  # Methods of built-in types.
+		tween.tween_callback(print.bind("Test"))  # Global functions.
+		[/codeblock]
+		[b]Note:[/b] [Dictionary] does not support the above due to ambiguity with keys.
+		[codeblock]
+		var dictionary = {"hello": "world"}
+
+		# This will not work, `clear` is treated as a key.
+		tween.tween_callback(dictionary.clear)
+
+		# This will work.
+		tween.tween_callback(Callable.create(dictionary, "clear"))
+		[/codeblock]
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>
@@ -69,6 +85,7 @@
 			<param index="1" name="method" type="StringName" />
 			<param index="1" name="method" type="StringName" />
 			<description>
 			<description>
 				Creates a new [Callable] for the method named [param method] in the specified [param object].
 				Creates a new [Callable] for the method named [param method] in the specified [param object].
+				[b]Note:[/b] For methods of built-in [Variant] types, use [method create] instead.
 			</description>
 			</description>
 		</constructor>
 		</constructor>
 	</constructors>
 	</constructors>
@@ -120,6 +137,15 @@
 				Calls the method represented by this [Callable]. Unlike [method call], this method expects all arguments to be contained inside the [param arguments] [Array].
 				Calls the method represented by this [Callable]. Unlike [method call], this method expects all arguments to be contained inside the [param arguments] [Array].
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="create" qualifiers="static">
+			<return type="Callable" />
+			<param index="0" name="variant" type="Variant" />
+			<param index="1" name="method" type="StringName" />
+			<description>
+				Creates a new [Callable] for the method named [param method] in the specified [param variant]. To represent a method of a built-in [Variant] type, a custom callable is used (see [method is_custom]). If [param variant] is [Object], then a standard callable will be created instead.
+				[b]Note:[/b] This method is always necessary for the [Dictionary] type, as property syntax is used to access its entries. You may also use this method when [param variant]'s type is not known in advance (for polymorphism).
+			</description>
+		</method>
 		<method name="get_bound_arguments" qualifiers="const">
 		<method name="get_bound_arguments" qualifiers="const">
 			<return type="Array" />
 			<return type="Array" />
 			<description>
 			<description>
@@ -160,7 +186,11 @@
 		<method name="is_custom" qualifiers="const">
 		<method name="is_custom" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>
-				Returns [code]true[/code] if this [Callable] is a custom callable. Custom callables are created from [method bind] or [method unbind]. In GDScript, lambda functions are also custom callables.
+				Returns [code]true[/code] if this [Callable] is a custom callable. Custom callables are used:
+				- for binding/unbinding arguments (see [method bind] and [method unbind]);
+				- for representing methods of built-in [Variant] types (see [method create]);
+				- for representing global, lambda, and RPC functions in GDScript;
+				- for other purposes in the core, GDExtension, and C#.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="is_null" qualifiers="const">
 		<method name="is_null" qualifiers="const">

+ 9 - 2
modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd

@@ -1,6 +1,13 @@
 func test():
 func test():
 	var array: Array = [1, 2, 3]
 	var array: Array = [1, 2, 3]
 	print(array)
 	print(array)
-	var callable: Callable = array.clear
-	callable.call()
+	var array_clear: Callable = array.clear
+	array_clear.call()
 	print(array)
 	print(array)
+
+	var dictionary: Dictionary = {1: 2, 3: 4}
+	print(dictionary)
+	# `dictionary.clear` is treated as a key.
+	var dictionary_clear := Callable.create(dictionary, &"clear")
+	dictionary_clear.call()
+	print(dictionary)

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

@@ -1,3 +1,5 @@
 GDTEST_OK
 GDTEST_OK
 [1, 2, 3]
 [1, 2, 3]
 []
 []
+{ 1: 2, 3: 4 }
+{  }