|
@@ -47,6 +47,93 @@ To create a thread, use the following code:
|
|
|
func _exit_tree():
|
|
|
thread.wait_to_finish()
|
|
|
|
|
|
+ .. code-tab:: cpp C++ .H File
|
|
|
+
|
|
|
+ #ifndef MULTITHREADING_DEMO_H
|
|
|
+ #define MULTITHREADING_DEMO_H
|
|
|
+
|
|
|
+ #include <godot_cpp/classes/node.hpp>
|
|
|
+ #include <godot_cpp/classes/thread.hpp>
|
|
|
+
|
|
|
+ namespace godot {
|
|
|
+ class MultithreadingDemo : public Node {
|
|
|
+ GDCLASS(MultithreadingDemo, Node);
|
|
|
+
|
|
|
+ private:
|
|
|
+ Ref<Thread> worker;
|
|
|
+
|
|
|
+ protected:
|
|
|
+ static void _bind_methods();
|
|
|
+ void _notification(int p_what);
|
|
|
+
|
|
|
+ public:
|
|
|
+ MultithreadingDemo();
|
|
|
+ ~MultithreadingDemo();
|
|
|
+
|
|
|
+ void demo_threaded_function();
|
|
|
+ };
|
|
|
+ } // namespace godot
|
|
|
+
|
|
|
+ #endif // MULTITHREADING_DEMO_H
|
|
|
+
|
|
|
+ .. code-tab:: cpp C++ .CPP File
|
|
|
+
|
|
|
+ #include "multithreading_demo.h"
|
|
|
+
|
|
|
+ #include <godot_cpp/classes/engine.hpp>
|
|
|
+ #include <godot_cpp/classes/os.hpp>
|
|
|
+ #include <godot_cpp/classes/time.hpp>
|
|
|
+ #include <godot_cpp/core/class_db.hpp>
|
|
|
+ #include <godot_cpp/variant/utility_functions.hpp>
|
|
|
+
|
|
|
+ using namespace godot;
|
|
|
+
|
|
|
+ void MultithreadingDemo::_bind_methods() {
|
|
|
+ ClassDB::bind_method(D_METHOD("threaded_function"), &MultithreadingDemo::demo_threaded_function);
|
|
|
+ }
|
|
|
+
|
|
|
+ void MultithreadingDemo::_notification(int p_what) {
|
|
|
+ // Prevents this from running in the editor, only during game mode. In Godot 4.3+ use Runtime classes.
|
|
|
+ if (Engine::get_singleton()->is_editor_hint()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (p_what) {
|
|
|
+ case NOTIFICATION_READY: {
|
|
|
+ worker.instantiate();
|
|
|
+ worker->start(callable_mp(this, &MultithreadingDemo::demo_threaded_function), Thread::PRIORITY_NORMAL);
|
|
|
+ } break;
|
|
|
+ case NOTIFICATION_EXIT_TREE: { // Thread must be disposed (or "joined"), for portability.
|
|
|
+ // Wait until it exits.
|
|
|
+ if (worker.is_valid()) {
|
|
|
+ worker->wait_to_finish();
|
|
|
+ }
|
|
|
+
|
|
|
+ worker.unref();
|
|
|
+ } break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ MultithreadingDemo::MultithreadingDemo() {
|
|
|
+ // Initialize any variables here.
|
|
|
+ }
|
|
|
+
|
|
|
+ MultithreadingDemo::~MultithreadingDemo() {
|
|
|
+ // Add your cleanup here.
|
|
|
+ }
|
|
|
+
|
|
|
+ void MultithreadingDemo::demo_threaded_function() {
|
|
|
+ UtilityFunctions::print("demo_threaded_function started!");
|
|
|
+ int i = 0;
|
|
|
+ uint64_t start = Time::get_singleton()->get_ticks_msec();
|
|
|
+ while (Time::get_singleton()->get_ticks_msec() - start < 5000) {
|
|
|
+ OS::get_singleton()->delay_msec(10);
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ UtilityFunctions::print("demo_threaded_function counted to: ", i, ".");
|
|
|
+ }
|
|
|
+
|
|
|
Your function will, then, run in a separate thread until it returns.
|
|
|
Even if the function has returned already, the thread must collect it, so call
|
|
|
:ref:`Thread.wait_to_finish()<class_Thread_method_wait_to_finish>`, which will
|
|
@@ -112,6 +199,99 @@ Here is an example of using a Mutex:
|
|
|
thread.wait_to_finish()
|
|
|
print("Counter is: ", counter) # Should be 2.
|
|
|
|
|
|
+ .. code-tab:: cpp C++ .H File
|
|
|
+
|
|
|
+ #ifndef MUTEX_DEMO_H
|
|
|
+ #define MUTEX_DEMO_H
|
|
|
+
|
|
|
+ #include <godot_cpp/classes/mutex.hpp>
|
|
|
+ #include <godot_cpp/classes/node.hpp>
|
|
|
+ #include <godot_cpp/classes/thread.hpp>
|
|
|
+
|
|
|
+ namespace godot {
|
|
|
+ class MutexDemo : public Node {
|
|
|
+ GDCLASS(MutexDemo, Node);
|
|
|
+
|
|
|
+ private:
|
|
|
+ int counter = 0;
|
|
|
+ Ref<Mutex> mutex;
|
|
|
+ Ref<Thread> thread;
|
|
|
+
|
|
|
+ protected:
|
|
|
+ static void _bind_methods();
|
|
|
+ void _notification(int p_what);
|
|
|
+
|
|
|
+ public:
|
|
|
+ MutexDemo();
|
|
|
+ ~MutexDemo();
|
|
|
+
|
|
|
+ void thread_function();
|
|
|
+ };
|
|
|
+ } // namespace godot
|
|
|
+
|
|
|
+ #endif // MUTEX_DEMO_H
|
|
|
+
|
|
|
+ .. code-tab:: cpp C++ .CPP File
|
|
|
+
|
|
|
+ #include "mutex_demo.h"
|
|
|
+
|
|
|
+ #include <godot_cpp/classes/engine.hpp>
|
|
|
+ #include <godot_cpp/classes/time.hpp>
|
|
|
+ #include <godot_cpp/core/class_db.hpp>
|
|
|
+ #include <godot_cpp/variant/utility_functions.hpp>
|
|
|
+
|
|
|
+ using namespace godot;
|
|
|
+
|
|
|
+ void MutexDemo::_bind_methods() {
|
|
|
+ ClassDB::bind_method(D_METHOD("thread_function"), &MutexDemo::thread_function);
|
|
|
+ }
|
|
|
+
|
|
|
+ void MutexDemo::_notification(int p_what) {
|
|
|
+ // Prevents this from running in the editor, only during game mode.
|
|
|
+ if (Engine::get_singleton()->is_editor_hint()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (p_what) {
|
|
|
+ case NOTIFICATION_READY: {
|
|
|
+ UtilityFunctions::print("Mutex Demo Counter is starting at: ", counter);
|
|
|
+ mutex.instantiate();
|
|
|
+ thread.instantiate();
|
|
|
+ thread->start(callable_mp(this, &MutexDemo::thread_function), Thread::PRIORITY_NORMAL);
|
|
|
+
|
|
|
+ // Increase value, protect it with Mutex.
|
|
|
+ mutex->lock();
|
|
|
+ counter += 1;
|
|
|
+ UtilityFunctions::print("Mutex Demo Counter is ", counter, " after adding with Mutex protection.");
|
|
|
+ mutex->unlock();
|
|
|
+ } break;
|
|
|
+ case NOTIFICATION_EXIT_TREE: { // Thread must be disposed (or "joined"), for portability.
|
|
|
+ // Wait until it exits.
|
|
|
+ if (thread.is_valid()) {
|
|
|
+ thread->wait_to_finish();
|
|
|
+ }
|
|
|
+ thread.unref();
|
|
|
+
|
|
|
+ UtilityFunctions::print("Mutex Demo Counter is ", counter, " at EXIT_TREE."); // Should be 2.
|
|
|
+ } break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ MutexDemo::MutexDemo() {
|
|
|
+ // Initialize any variables here.
|
|
|
+ }
|
|
|
+
|
|
|
+ MutexDemo::~MutexDemo() {
|
|
|
+ // Add your cleanup here.
|
|
|
+ }
|
|
|
+
|
|
|
+ // Increment the value from the thread, too.
|
|
|
+ void MutexDemo::thread_function() {
|
|
|
+ mutex->lock();
|
|
|
+ counter += 1;
|
|
|
+ mutex->unlock();
|
|
|
+ }
|
|
|
+
|
|
|
Semaphores
|
|
|
----------
|
|
|
|
|
@@ -188,3 +368,134 @@ ready to be processed:
|
|
|
|
|
|
# Print the counter.
|
|
|
print("Counter is: ", counter)
|
|
|
+
|
|
|
+ .. code-tab:: cpp C++ .H File
|
|
|
+
|
|
|
+ #ifndef SEMAPHORE_DEMO_H
|
|
|
+ #define SEMAPHORE_DEMO_H
|
|
|
+
|
|
|
+ #include <godot_cpp/classes/mutex.hpp>
|
|
|
+ #include <godot_cpp/classes/node.hpp>
|
|
|
+ #include <godot_cpp/classes/semaphore.hpp>
|
|
|
+ #include <godot_cpp/classes/thread.hpp>
|
|
|
+
|
|
|
+ namespace godot {
|
|
|
+ class SemaphoreDemo : public Node {
|
|
|
+ GDCLASS(SemaphoreDemo, Node);
|
|
|
+
|
|
|
+ private:
|
|
|
+ int counter = 0;
|
|
|
+ Ref<Mutex> mutex;
|
|
|
+ Ref<Semaphore> semaphore;
|
|
|
+ Ref<Thread> thread;
|
|
|
+ bool exit_thread = false;
|
|
|
+
|
|
|
+ protected:
|
|
|
+ static void _bind_methods();
|
|
|
+ void _notification(int p_what);
|
|
|
+
|
|
|
+ public:
|
|
|
+ SemaphoreDemo();
|
|
|
+ ~SemaphoreDemo();
|
|
|
+
|
|
|
+ void thread_function();
|
|
|
+ void increment_counter();
|
|
|
+ int get_counter();
|
|
|
+ };
|
|
|
+ } // namespace godot
|
|
|
+
|
|
|
+ #endif // SEMAPHORE_DEMO_H
|
|
|
+
|
|
|
+ .. code-tab:: cpp C++ .CPP File
|
|
|
+
|
|
|
+ #include "semaphore_demo.h"
|
|
|
+
|
|
|
+ #include <godot_cpp/classes/engine.hpp>
|
|
|
+ #include <godot_cpp/classes/time.hpp>
|
|
|
+ #include <godot_cpp/core/class_db.hpp>
|
|
|
+ #include <godot_cpp/variant/utility_functions.hpp>
|
|
|
+
|
|
|
+ using namespace godot;
|
|
|
+
|
|
|
+ void SemaphoreDemo::_bind_methods() {
|
|
|
+ ClassDB::bind_method(D_METHOD("thread_function"), &SemaphoreDemo::thread_function);
|
|
|
+ }
|
|
|
+
|
|
|
+ void SemaphoreDemo::_notification(int p_what) {
|
|
|
+ // Prevents this from running in the editor, only during game mode.
|
|
|
+ if (Engine::get_singleton()->is_editor_hint()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (p_what) {
|
|
|
+ case NOTIFICATION_READY: {
|
|
|
+ UtilityFunctions::print("Semaphore Demo Counter is starting at: ", counter);
|
|
|
+ mutex.instantiate();
|
|
|
+ semaphore.instantiate();
|
|
|
+ exit_thread = false;
|
|
|
+
|
|
|
+ thread.instantiate();
|
|
|
+ thread->start(callable_mp(this, &SemaphoreDemo::thread_function), Thread::PRIORITY_NORMAL);
|
|
|
+
|
|
|
+ increment_counter(); // Call increment counter to test.
|
|
|
+ } break;
|
|
|
+ case NOTIFICATION_EXIT_TREE: { // Thread must be disposed (or "joined"), for portability.
|
|
|
+ // Set exit condition to true.
|
|
|
+ mutex->lock();
|
|
|
+ exit_thread = true; // Protect with Mutex.
|
|
|
+ mutex->unlock();
|
|
|
+
|
|
|
+ // Unblock by posting.
|
|
|
+ semaphore->post();
|
|
|
+
|
|
|
+ // Wait until it exits.
|
|
|
+ if (thread.is_valid()) {
|
|
|
+ thread->wait_to_finish();
|
|
|
+ }
|
|
|
+ thread.unref();
|
|
|
+
|
|
|
+ // Print the counter.
|
|
|
+ UtilityFunctions::print("Semaphore Demo Counter is ", get_counter(), " at EXIT_TREE.");
|
|
|
+ } break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ SemaphoreDemo::SemaphoreDemo() {
|
|
|
+ // Initialize any variables here.
|
|
|
+ }
|
|
|
+
|
|
|
+ SemaphoreDemo::~SemaphoreDemo() {
|
|
|
+ // Add your cleanup here.
|
|
|
+ }
|
|
|
+
|
|
|
+ // Increment the value from the thread, too.
|
|
|
+ void SemaphoreDemo::thread_function() {
|
|
|
+ while (true) {
|
|
|
+ semaphore->wait(); // Wait until posted.
|
|
|
+
|
|
|
+ mutex->lock();
|
|
|
+ bool should_exit = exit_thread; // Protect with Mutex.
|
|
|
+ mutex->unlock();
|
|
|
+
|
|
|
+ if (should_exit) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex->lock();
|
|
|
+ counter += 1; // Increment counter, protect with Mutex.
|
|
|
+ mutex->unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void SemaphoreDemo::increment_counter() {
|
|
|
+ semaphore->post(); // Make the thread process.
|
|
|
+ }
|
|
|
+
|
|
|
+ int SemaphoreDemo::get_counter() {
|
|
|
+ mutex->lock();
|
|
|
+ // Copy counter, protect with Mutex.
|
|
|
+ int counter_value = counter;
|
|
|
+ mutex->unlock();
|
|
|
+ return counter_value;
|
|
|
+ }
|
|
|
+
|