From d90c9db27fdcbbdd6e195ec1ca106f6f7513004e Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Wed, 28 Feb 2024 12:53:34 +0300 Subject: [PATCH] Core: Add `Callable.create` static method for `Variant` callables --- core/variant/callable.cpp | 32 ++++++++++++----- core/variant/callable.h | 2 ++ core/variant/variant_call.cpp | 1 + doc/classes/Callable.xml | 34 +++++++++++++++++-- .../features/builtin_method_as_callable.gd | 11 ++++-- .../features/builtin_method_as_callable.out | 2 ++ 6 files changed, 69 insertions(+), 13 deletions(-) diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index 55f687bdf9c9..6bad6f5a5bb2 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -30,10 +30,11 @@ #include "callable.h" -#include "callable_bind.h" #include "core/object/object.h" #include "core/object/ref_counted.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 { 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) { - if (p_method == StringName()) { + if (unlikely(p_method == StringName())) { 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; - 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(); @@ -342,9 +356,9 @@ Callable::Callable(const Object *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; - 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; @@ -352,9 +366,9 @@ Callable::Callable(ObjectID p_object, const StringName &p_method) { } Callable::Callable(CallableCustom *p_custom) { - if (p_custom->referenced) { + if (unlikely(p_custom->referenced)) { object = 0; - ERR_FAIL_MSG("Callable custom is already referenced"); + ERR_FAIL_MSG("Callable custom is already referenced."); } p_custom->referenced = true; object = 0; //ensure object is all zero, since pointer may be 32 bits diff --git a/core/variant/callable.h b/core/variant/callable.h index 38872b71ef2d..bba69d453e7a 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -125,6 +125,8 @@ class Callable { operator String() const; + static Callable create(const Variant &p_variant, const StringName &p_method); + Callable(const Object *p_object, const StringName &p_method); Callable(ObjectID p_object, const StringName &p_method); Callable(CallableCustom *p_custom); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index b551a7059e8c..eaf51aa4ca72 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2038,6 +2038,7 @@ static void _register_variant_builtin_methods() { /* Callable */ + bind_static_method(Callable, create, sarray("variant", "method"), varray()); bind_method(Callable, callv, sarray("arguments"), varray()); bind_method(Callable, is_null, sarray(), varray()); bind_method(Callable, is_custom, sarray(), varray()); diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index 8dd0cfa1357b..bd56d6ab8063 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -4,7 +4,7 @@ A built-in type representing a method or a standalone function. - [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] [codeblocks] [gdscript] @@ -46,6 +46,22 @@ # Prints "Attack!", when the button_pressed signal is emitted. button_pressed.connect(func(): print("Attack!")) [/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] @@ -69,6 +85,7 @@ 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. @@ -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]. + + + + + + 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). + + @@ -160,7 +186,11 @@ - 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#. diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd index e4016c011988..cb5ea827f6a1 100644 --- a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd +++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd @@ -1,6 +1,13 @@ func test(): var array: Array = [1, 2, 3] print(array) - var callable: Callable = array.clear - callable.call() + var array_clear: Callable = array.clear + array_clear.call() 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) diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out index c4182b38e975..c12984ca3757 100644 --- a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out +++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out @@ -1,3 +1,5 @@ GDTEST_OK [1, 2, 3] [] +{ 1: 2, 3: 4 } +{ }