Gdextension C++ Async?

Godot Version

4.2

Question

Hi. Trying to write all my game in C++ (yes, what a trip), Is there a way to use async, yield or threads? Example https://www.youtube.com/watch?v=yZQStB6nHuI

// GDscript
$AnimationPlayer.play('dissolve')
yield($AnimationPlayer, 'animation_finished')
get_tree().change_scene(target)
$AnimationPlayer.play_backwards('dissolve')

// C#
public async void ChangeScene(string target)
  {
    anim.Play("Dissolve");
    await ToSignal(anim, "animation_finished");
    GetTree().ChangeSceneToFile(target);
    anim.PlayBackwards("Dissolve");
  }

But in C++, I tried to use godot::thread, std::threads, std::async. But if I use the standard C++ libs, godot complains.

2 Likes

I would if I had time, but why not look at the goodots source code on how they implemented the gdscript await operator.

Please update this if you figure this out – I’m just playing around with GDExtension and C++ right now, but I’m getting an immediate crash when trying to use std::thread and haven’t been able to find much about C++ threading in GDExtensions.

I give up, and currently porting my game to Unity. Find out to many bugs, even using GDscript, C# or C++. I will still using godot, but for now, Unity feels like I can finish my project.
With the C++ version, I noticed that you cant build the entire game in C++: trying to use autoloads/singletons, threads, ahh~~~ I don’t remember the other points but there was a lot, the C++ version is more like to develop for godot not for your project.

  • Note: Also I am working in a SDL2 project with C++ and I am using ranges and views, threads/async, coroutines, for(const auto& v : somecontainer) that in godot-cpp can’t use it.
1 Like

Elaborate? The tutorial on using multiple threads seems to work when it’s been converted from GDScript to GDExtension.

1 Like

Thanks! I’m trying to convert from the GDScript tutorial to GDExtension, but I’m getting tripped up on how to convert the calls. So for example I don’t know how to convert:
thread = Thread.new()
thread.start(_thread_function.bind(“Wafflecopter”))

I found in godot-voxel-master on GitHub, they had to make a helper function for GDExtension threads: godot_voxel/util/thread/godot_thread_helper.h at master · Zylann/godot_voxel · GitHub

If you got it working and could share the code I would really appreciate it!

The object has to be registered. I believe I’ve mentioned in another thread that callable_mp is a recent addition that may not be available for earlier versions. However, it should work with the other callable if the one I’m using isn’t available.

test_threads.h

Summary
#ifndef TESTTHREADS_H
#define TESTTHREADS_H

#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/thread.hpp>

using namespace godot;

class TestThreads : public Node {
	GDCLASS(TestThreads, Node);

private:
	Ref<Thread> thr;

protected:
	static void _bind_methods();
	void _notification(int p_what);

public:
	void run_calcs();
};

#endif // TESTTHREADS_H

test_threads.cpp

Summary
#include "test_threads.h"

#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/time.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

void TestThreads::_bind_methods() {
	ClassDB::bind_method(D_METHOD("run_calcs"), &TestThreads::run_calcs);
}

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

	switch (p_what) {
		case NOTIFICATION_READY: {
			thr.instantiate();
			thr->start(callable_mp(this, &TestThreads::run_calcs), Thread::PRIORITY_NORMAL);
		} break;
		case NOTIFICATION_EXIT_TREE: {
			if (thr.is_valid() && thr->is_alive()) {
				thr->wait_to_finish();
			}

			thr->unreference();
		};
	}
}

void TestThreads::run_calcs() {
	int i = 0;
	uint64_t start = Time::get_singleton()->get_ticks_msec();
	while (Time::get_singleton()->get_ticks_msec() - start < 5000) {
		i++;
	}

	UtilityFunctions::print("I counted to ", i, ".");

}```
4 Likes

You are amazing!! I super appreciate the help – makes a lot of sense now that I see it but I was struggling :smiley: I will share my repo here soon for anyone looking in the future. Thanks again!!

1 Like

For future reference, async and yield doesn’t exist in GDExtension. Instead, you can use signals and callables to create a similar effect.

#ifndef TEST_H
#define TEST_H

#include <godot_cpp/classes/animation_player.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/scene_tree.hpp>

using namespace godot;

class Test : public Node {
	GDCLASS(Test, Node);

private:
	AnimationPlayer *animation_player = nullptr;

protected:
	static void _bind_methods() {
		ClassDB::bind_method(D_METHOD("ready"), &Test::ready);
		ClassDB::bind_method(D_METHOD("on_animation_finished"), &Test::on_animation_finished);
	};

	void _notification(int p_what) {
		switch (p_what) {
			case NOTIFICATION_READY: {
				animation_player = get_node<AnimationPlayer>("path_to_animation_player");
				ready();
			} break;
		}
	};

public:
	void ready() {
		// One shots disconnect after being shot once.
		animation_player->connect("animation_finished", callable_mp(this, &Test::on_animation_finished), ConnectFlags::CONNECT_ONE_SHOT);
		animation_player->play("dissolve");
	};

	void on_animation_finished(String target) {
		get_tree()->change_scene_to_file(target);
		animation_player->play_backwards("dissolve");
	};
};

#endif // TEST_H

This should work. You can ignore everything but the ready and on_animation_finished functions. After ready is called, a Callable is made that will be called when the animation has finished. It’s important that it one shots, otherwise the callable will stay connected, which will add to the number of times on_animation_finished is called when ready is called. After the signal signals and on_animation_finished is called, the code effectively resumes. The Callable is disconnected automatically because it is one shot.

4 Likes

Thanks so much! I’m working on a clean github project to share and potentially update the official documentation with all this great C++ GDExtension info.

2 Likes

It’s probably a mess, but here’s the GitHub repo I set up for this. I’m planning on doing a little more with it, but I want to keep it as a simple demo project.

2 Likes