1
\$\begingroup\$

I followed an old tutorial about a card game and, after finishing it, I started removing some code I didn't like or it was simply not compatible with Godot 4.

Anything went fine, except for one thing.

In the game are declared two unneeded one-shot timers: they do the exact same thing, with the same delay but, once fired, they must call two different functions.

I refactored the code to have only one timer.

One solution worked, but I wanted to know why the other one didn't.

In the _ready function there's a function setupTimers, where I have:

c = Callable(self, method)
flipTimer.connect("timeout", c)

method is a global containing the -variable- string name of a function:

I change its value just before calling flipTimer.start(1), but problem is, once declared the Callable, method takes its current value and it cannot change anymore (I even tried using a function to return the current method value).

Is there any way to make it work the way I hoped?

The solution that worked is a little bit different:

  • I provide the name of a function directly;
  • the function is called on timeout;
  • inside the body of the function I expression.execute the function names;

This way it works, but why the other don't? I suppose Callable stores a value that can't be variable but I'd like to know if there's a way to get around this limitation.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

The types Callable, String and StringName for that matter, are all value types. Anywhere you pass them, you are passing a copy. StringName is an internal string.

So when you pass the name of the method to create the Callable, the Callable has a copy. And when you pass the Callable to connect the signals, that is a copy.

To reiterate, they are not references. So changing the string that has the name of the method you passed to the Callable has nothing to do with the Callable, nor the signal connection.

In fact, Callable, and StringName are immutable. String can be manipulated, but we rarely do that. So no, you cannot change the method that the Callable points to.


By the way, this code:

c = Callable(self, method)
flipTimer.connect("timeout", c)

Is equivalent to this:

c = Callable(self, method)
flipTimer.timeout.connect(c)

You don't need to use a String to reference signals anymore.


Now, since Callable are value types, any local Callable is freed when it goes out of scope. So they work very well for short lived values.

Thus, you don't need to use expression.execute to call a method by name, you can do this:

Callable(obj, method_name).call()

In fact, you don't need a Callable for this, since Object has a call method:

obj.call(method_name)

Which - by the way - was valid in Godot 3.


Anyway, you have two one shot Timers. Instead of trying to combine them into one one shot Timer, I suggest you use the SceneTreeTimers:

var c := Callable(obj, "actual_method")
get_tree().create_timer(timeout_seconds).timeout.connect(c)

In fact, since you would know which one shot Timer you are supposed to call, you can connect the method directly:

get_tree().create_timer(timeout_seconds).timeout.connect(obj.actual_method)

Notice that I'm not adding () after the method name, and thus I'm not calling it. That is obj.actual_method() calls the method, obj.actual_method references the method (the equivalent of making a Callable).

Godot will free the SceneTreeTimer once they emit their timeout signal.

Not not free your SceneTreeTimer manually, otherwise others timers will start behaving incorrectly due to Godot still referencing invalid memory (where a timer created afterwards could be allocated into).


You might also be interested in anonymous inline methods ("lambda functions"). For example:

get_tree().create_timer(timeout_seconds).timeout.connect(
    func():
        prints("hello world")
)

It could also be all in a single line.

The lambda function is also a Callable.

\$\endgroup\$
1
  • \$\begingroup\$ I didn't know a lot of things: for example, I didn't know there is a StringName type and a SceneTreeTimer. I'm really new to GDScript and I bring with me a whole range of old (bad or good, it's not always clear) habits in programming. I found lots of tutorials using GDScript code that is not working anymore and code that still works (like in the signal signature you point out to me). Sometimes it is not easy to untangle such a confusing situation. Thanks! \$\endgroup\$ Commented Apr 18, 2023 at 7:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .