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.