Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update using_multiple_threads.rst #8752

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 311 additions & 0 deletions tutorials/performance/using_multiple_threads.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
----------

Expand Down Expand Up @@ -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;
}