Using Thread in a GDExtension?

Godot Version

v4.2.2.stable.official [15073afe3]

Question

I’m in the process of rewriting a GDScript game component as a GDExtension with C++ using Godot 4.2.

The original code uses Godot’s Thread class to avoid blocking the user interface, and it’s working as expected. The thread is waited upon, and subsequent calls to this component also work properly, without any errors or warnings.

Without knowing much about C++ I’ve been successful to port my GDScript to to C++ and build it as a GDExtension. I must confess, while this wasn’t a completely painless process, I’m truly impressed by not only the immense performance gain (by Jove! C++ is truly a beast!), but the entire GDExtension workflow is not nearly as hard as I’ve anticipated.

I knew that the threading part would be the most complex task, so first I simply omitted those parts and made the code work as a syncronous, UI blocking operation. The result was what I expected, so then I’ve attempted to implement threading, but I’m kind of stuck, so I’m looking for (no, not “pointers”, haha) some help.

Let me show my original code (only the relevant parts):

extends Node

var thread := Thread.new()

func _exit_tree() -> void:
    if thread.is_alive():
        thread.wait_to_finish()

func start_doing_the_heavy_job() -> void:
    if thread.start(threaded_operation) != OK:
        printerr("Could not start thread.")

func threaded_operation() -> void:
    # this is where the computations happen
    all_is_well.call_deferred()

func all_is_well() -> void:
    thread.wait_to_finish()
    print("Threaded operation complete, ready to start again, when needed.")

First, I had no idea how to create a Thread object in C++. After some research and dismissing quite a few bad hallucinations from various chatbots, this is what I have (again, I’m trying to only show what I think is relevant):

my_extension.h

Ref<Thread> thread;

my_extension.cpp

void MyExtension::_notification(int p_what) {
    if (Engine::get_singleton()->is_editor_hint()) {
        return;
    }

    switch (p_what) {
        case NOTIFICATION_EXIT_TREE: {
            if (thread.is_valid()) {
                thread->wait_to_finish();
            }

            thread.unref();
        }
    }
}

void MyExtension::start_doing_the_heavy_job() {
    thread.instantiate();
    thread->start(callable_mp(this, &MyExtension::threaded_operation), Thread::PRIORITY_NORMAL);
}

void MyExtension::threaded_operation() {
    // this is where the computations happen
    all_is_well() // also tried as `call_deferred("all_is_well");` but didn't work, see below.
}

void MyExtension::all_is_well() {
    thread->call_deferred("wait_to_finish");
    thread.unref();
}

This sort of works since I do get the expected results, but I see these warnings for every occasion:

W 0:00:03:0186   ~Thread: A Thread object is being destroyed without its completion having been realized.
Please call wait_to_finish() on it to ensure correct cleanup.
<C++ Source>   core/os/thread.cpp:104 @ ~Thread()

I have, of course, tried to call the all_is_well function deferred (call_deferred("all_is_well");), but then I got the following error (and I didn’t get the expected results):

E 0:00:03:0243   _call_function: Error calling deferred method: 'MyExtension::all_is_well': Method not found.
<C++ Source>   core/object/message_queue.cpp:223 @ _call_function()

I’m very curious as to how this behavior should be written properly.

void MyExtension::all_is_well() {
    thread->call_deferred("wait_to_finish");
    thread.unref();
}

You are deferring “wait_to_finish” on the next frame it will wait to finish. But you immediately unref and free the object. In your gdscript the entire function was deferred, so next frame it would wait_to_finish and then unref.

If you want to defer the “all_is_well” function I believe you will have to bind it as a method, and “all_is_well” is not the same as “finding_words_finished” do you have a function named that? (I see the edit)

1 Like

I’m sorry, I have left “finding_words_finished” in the sample code by accident. That’s the actual name of the function that waits for the computation in my component, and in the sample I’ve named it “all_is_well”.

I think I also just wish to defer the calling of “all_is_well”, as that’s what I tried to do, without luck. :slight_smile:

Can you please elaborate on what you mean by “bind it as a method”?

ClassDB::bind_method(D_METHOD("all_is_well"), &MyExtension::all_is_well);

These lines in your _bind_methods function

1 Like

That’s the solution. Thank you very much! :slight_smile: I think I even understand why this had to be done. :slight_smile:

I am glad it wasn’t Thread specific (not my favorite to debug)! and I’m glad you saw such a performance gain from C++!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.