godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.54k stars 21.08k forks source link

User defined callbacks #30997

Closed lawnjelly closed 5 years ago

lawnjelly commented 5 years ago

Issue description: This is something that came up today on irc #godotengine-devel, and probably a result of me being a newbie to Godot, so I'm probably going to make a fool of myself asking :smile: :

I was looking for a way of having a user definable callback function that could be written in e.g. gdscript, and called from the core. I could get this working by adding a specific function to set the callback, e.g.

Engine.set_myparticularcallback(Node * pNode, String user_func_name);

Then I got thinking, it seems a bit ridiculous writing a unique function to set this one particular callback, because there must be a lot of cases where it would be useful for user code to create a callback (without, for example creating a new node especially). We can already do things like call_deferred for example, so function calls are already 'packagable' things.

So I was asking Akien and IronicallySerious (thinking maybe I'm using the wrong paradigm) and it turned out that it is possible that this same problem has come up before several times, and there might be a need for a generic means of solving it. We were suspecting that there must (surely?) already be a solution for this (maybe in a different guise), but me and IronicallySerious weren't aware of it.

Anyway to continue this flight of fancy, my incredibly naive generic version 0.01 of this would be something like this:

Engine.set_user_callback(String callback_class, Node * pNode, String user_func_name);

So pretty similar to the original version, except that it would be possible to add new callbacks without having to add a new gdscript command each time. You could of course do something more advanced but the most basic conceptual level could just be something like this:

Engine::set_user_callback(String callback_class, Node * pNode, String user_func_name)
{
    if (callback_class == "bark_like_a_dog")
    {
        // do something here .. check parameters etc
        // set the callback
    }

    if (callback_class == "squawk_like_a_chicken")
    {
        // do something else
    }
}

Of course you could make it data driven, and have the concerned code register interest in a particular callback, and be able to look it up quickly on demand if it had been set, have it print an error if the callback_class wasn't found etc etc. This could potentially be done from other parts of the code, not just the core, e.g. modules, GDNative etc.

So the question is, is there already a mechanism in place for doing this kind of thing?

bojidar-bg commented 5 years ago

I assume signal-s are not enough?

lawnjelly commented 5 years ago

Ah that maybe the answer, I've not examined the signals mechanism! :+1: But can the signals system return a value in realtime (i.e. be used directly as a function call) or is it tied to an event queue or something?

Will investigate.

volzhs commented 5 years ago
extends Node2D

signal my_signal

func _ready():
    connect("my_signal", self, "on_test")
    test()

func test():
    # do something
    emit_signal("my_signal", 1, "a")

func on_test(a, b):
    prints(a, b)  # prints 1 a
lawnjelly commented 5 years ago

Am hopeful that this is the solution! Might have to hack in a return value but that should be no big deal. :+1: Thanks guys! :smile: (yes I feel a little silly lol).

Will leave this open just for now to check it works okay as the solution and to see if it will work for IronicallySerious' case as well.

Zylann commented 5 years ago

@lawnjelly actually signals are useful the other way around. If the engine needs to call some arbitrary code when specific events happen, then the engine simply exposes signals, and you then connect those signals to functions of your script... isn't it? If you care about return values, that could be done with a Map<String, FuncRef> where the key is the name of the "signal" and the value is the function to call.

twaritwaikar commented 5 years ago

@lawnjelly I think the architecture I created in my GSoC project covers the return value aspect of this without using signals. This should work in a similar way for GDScript too.

I couldn't have used a map since I was working with GDNative. The map would have been required to be accessed from the engine too and we found out that the engine already has the ->call() to handle return values in a cleaner way.

I explained the entire solution in some detail towards the end of this https://github.com/IronicallySerious/gsoc-godot-vcs-devlogs/blob/master/2019-8-2.md