Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Add Callable.create static method for Variant callables #88948

Merged
merged 1 commit into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions core/variant/callable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -327,34 +328,47 @@ 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.");
dalexeev marked this conversation as resolved.
Show resolved Hide resolved

switch (p_variant.get_type()) {
case Variant::NIL:
dalexeev marked this conversation as resolved.
Show resolved Hide resolved
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)));
}
}
dalexeev marked this conversation as resolved.
Show resolved Hide resolved

Callable::Callable(const Object *p_object, const StringName &p_method) {
if (p_method == StringName()) {
if (unlikely(p_method == StringName())) {
dalexeev marked this conversation as resolved.
Show resolved Hide resolved
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)) {
dalexeev marked this conversation as resolved.
Show resolved Hide resolved
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();
method = 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;
method = 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
Expand Down
2 changes: 2 additions & 0 deletions core/variant/callable.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ class Callable {

operator String() const;

static Callable create(const Variant &p_variant, const StringName &p_method);
dalexeev marked this conversation as resolved.
Show resolved Hide resolved

Callable(const Object *p_object, const StringName &p_method);
Callable(ObjectID p_object, const StringName &p_method);
Callable(CallableCustom *p_custom);
Expand Down
1 change: 1 addition & 0 deletions core/variant/variant_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
34 changes: 32 additions & 2 deletions doc/classes/Callable.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
A built-in type representing a method or a standalone function.
</brief_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]
[codeblocks]
[gdscript]
Expand Down Expand Up @@ -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]
</description>
<tutorials>
</tutorials>
Expand All @@ -69,6 +85,7 @@
<param index="1" name="method" type="StringName" />
<description>
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>
</constructor>
</constructors>
Expand Down Expand Up @@ -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].
</description>
</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">
<return type="Array" />
<description>
Expand Down Expand Up @@ -160,7 +186,11 @@
<method name="is_custom" qualifiers="const">
<return type="bool" />
<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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be better to reword the list to describe how custom Callables are created, how to create and obtain them, rather than list what they're used for.

I have no suggestions at the moment.

- 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>
</method>
<method name="is_null" qualifiers="const">
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
GDTEST_OK
[1, 2, 3]
[]
{ 1: 2, 3: 4 }
{ }
Loading