Add multi-threaded NavMesh baking to NavigationServer

Adds multi-threaded NavMesh baking to NavigationServer.
This commit is contained in:
smix8 2023-07-07 15:59:10 +02:00
parent f2acfb1ffc
commit 8686e84b44
11 changed files with 205 additions and 75 deletions

View File

@ -216,6 +216,15 @@
Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called. Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called.
</description> </description>
</method> </method>
<method name="bake_from_source_geometry_data_async">
<return type="void" />
<param index="0" name="navigation_mesh" type="NavigationMesh" />
<param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData3D" />
<param index="2" name="callback" type="Callable" default="Callable()" />
<description>
Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data] as an async task running on a background thread. After the process is finished the optional [param callback] will be called.
</description>
</method>
<method name="free_rid"> <method name="free_rid">
<return type="void" /> <return type="void" />
<param index="0" name="rid" type="RID" /> <param index="0" name="rid" type="RID" />

View File

@ -1991,6 +1991,12 @@
<member name="navigation/avoidance/thread_model/avoidance_use_multiple_threads" type="bool" setter="" getter="" default="true"> <member name="navigation/avoidance/thread_model/avoidance_use_multiple_threads" type="bool" setter="" getter="" default="true">
If enabled the avoidance calculations use multiple threads. If enabled the avoidance calculations use multiple threads.
</member> </member>
<member name="navigation/baking/thread_model/baking_use_high_priority_threads" type="bool" setter="" getter="" default="true">
If enabled and async navmesh baking uses multiple threads the threads run with high priority.
</member>
<member name="navigation/baking/thread_model/baking_use_multiple_threads" type="bool" setter="" getter="" default="true">
If enabled the async navmesh baking uses multiple threads.
</member>
<member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="32768"> <member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="32768">
Maximum number of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. Maximum number of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member> </member>

View File

@ -930,7 +930,7 @@ COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) {
obstacle->set_avoidance_layers(p_layers); obstacle->set_avoidance_layers(p_layers);
} }
void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
@ -942,26 +942,26 @@ void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh>
#endif // _3D_DISABLED #endif // _3D_DISABLED
} }
void GodotNavigationServer::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) { void GodotNavigationServer::bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D."); ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
if (!p_source_geometry_data->has_data()) {
p_navigation_mesh->clear();
if (p_callback.is_valid()) {
Callable::CallError ce;
Variant result;
p_callback.callp(nullptr, 0, result, ce);
}
return;
}
ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton()); ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
#endif // _3D_DISABLED #endif // _3D_DISABLED
} }
void GodotNavigationServer::bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
#ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback);
#endif // _3D_DISABLED
}
COMMAND_1(free, RID, p_object) { COMMAND_1(free, RID, p_object) {
if (map_owner.owns(p_object)) { if (map_owner.owns(p_object)) {
NavMap *map = map_owner.get_or_null(p_object); NavMap *map = map_owner.get_or_null(p_object);
@ -1093,6 +1093,16 @@ void GodotNavigationServer::process(real_t p_delta_time) {
return; return;
} }
#ifndef _3D_DISABLED
// Sync finished navmesh bakes before doing NavMap updates.
if (navmesh_generator_3d) {
navmesh_generator_3d->sync();
// Finished bakes emit callbacks and users might have reacted to those.
// Flush queue again so users do not have to wait for the next sync.
flush_queries();
}
#endif // _3D_DISABLED
int _new_pm_region_count = 0; int _new_pm_region_count = 0;
int _new_pm_agent_count = 0; int _new_pm_agent_count = 0;
int _new_pm_link_count = 0; int _new_pm_link_count = 0;

View File

@ -228,8 +228,9 @@ public:
virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override; virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override;
COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers); COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers);
virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override; virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
COMMAND_1(free, RID, p_object); COMMAND_1(free, RID, p_object);

View File

@ -32,6 +32,7 @@
#include "nav_mesh_generator_3d.h" #include "nav_mesh_generator_3d.h"
#include "core/config/project_settings.h"
#include "core/math/convex_hull.h" #include "core/math/convex_hull.h"
#include "core/os/thread.h" #include "core/os/thread.h"
#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h"
@ -62,7 +63,12 @@
NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr; NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr;
Mutex NavMeshGenerator3D::baking_navmesh_mutex; Mutex NavMeshGenerator3D::baking_navmesh_mutex;
Mutex NavMeshGenerator3D::generator_task_mutex;
bool NavMeshGenerator3D::use_threads = true;
bool NavMeshGenerator3D::baking_use_multiple_threads = true;
bool NavMeshGenerator3D::baking_use_high_priority_threads = true;
HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes; HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes;
HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks;
NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() { NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
return singleton; return singleton;
@ -71,15 +77,67 @@ NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
NavMeshGenerator3D::NavMeshGenerator3D() { NavMeshGenerator3D::NavMeshGenerator3D() {
ERR_FAIL_COND(singleton != nullptr); ERR_FAIL_COND(singleton != nullptr);
singleton = this; singleton = this;
baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads");
baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads");
// Using threads might cause problems on certain exports or with the Editor on certain devices.
// This is the main switch to turn threaded navmesh baking off should the need arise.
use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint();
} }
NavMeshGenerator3D::~NavMeshGenerator3D() { NavMeshGenerator3D::~NavMeshGenerator3D() {
cleanup(); cleanup();
} }
void NavMeshGenerator3D::sync() {
if (generator_tasks.size() == 0) {
return;
}
baking_navmesh_mutex.lock();
generator_task_mutex.lock();
LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
finished_task_ids.push_back(E.key);
NavMeshGeneratorTask3D *generator_task = E.value;
DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
baking_navmeshes.erase(generator_task->navigation_mesh);
if (generator_task->callback.is_valid()) {
generator_emit_callback(generator_task->callback);
}
memdelete(generator_task);
}
}
for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
generator_tasks.erase(finished_task_id);
}
generator_task_mutex.unlock();
baking_navmesh_mutex.unlock();
}
void NavMeshGenerator3D::cleanup() { void NavMeshGenerator3D::cleanup() {
baking_navmesh_mutex.lock(); baking_navmesh_mutex.lock();
generator_task_mutex.lock();
baking_navmeshes.clear(); baking_navmeshes.clear();
for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
NavMeshGeneratorTask3D *generator_task = E.value;
memdelete(generator_task);
}
generator_tasks.clear();
generator_task_mutex.unlock();
baking_navmesh_mutex.unlock(); baking_navmesh_mutex.unlock();
} }
@ -87,7 +145,7 @@ void NavMeshGenerator3D::finish() {
cleanup(); cleanup();
} }
void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { void NavMeshGenerator3D::parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
ERR_FAIL_COND(!Thread::is_main_thread()); ERR_FAIL_COND(!Thread::is_main_thread());
ERR_FAIL_COND(!p_navigation_mesh.is_valid()); ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(p_root_node == nullptr); ERR_FAIL_COND(p_root_node == nullptr);
@ -101,19 +159,25 @@ void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p
} }
} }
void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) { void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
ERR_FAIL_COND(!p_navigation_mesh.is_valid()); ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(!p_source_geometry_data.is_valid()); ERR_FAIL_COND(!p_source_geometry_data.is_valid());
ERR_FAIL_COND(!p_source_geometry_data->has_data());
if (!p_source_geometry_data->has_data()) {
p_navigation_mesh->clear();
if (p_callback.is_valid()) {
generator_emit_callback(p_callback);
}
return;
}
baking_navmesh_mutex.lock(); baking_navmesh_mutex.lock();
if (baking_navmeshes.has(p_navigation_mesh)) { if (baking_navmeshes.has(p_navigation_mesh)) {
baking_navmesh_mutex.unlock(); baking_navmesh_mutex.unlock();
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish."); ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
} else {
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
} }
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data); generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
@ -126,6 +190,51 @@ void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_na
} }
} }
void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(!p_source_geometry_data.is_valid());
if (!p_source_geometry_data->has_data()) {
p_navigation_mesh->clear();
if (p_callback.is_valid()) {
generator_emit_callback(p_callback);
}
return;
}
if (!use_threads) {
bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
return;
}
baking_navmesh_mutex.lock();
if (baking_navmeshes.has(p_navigation_mesh)) {
baking_navmesh_mutex.unlock();
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
return;
}
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
generator_task_mutex.lock();
NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
generator_task->callback = p_callback;
generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D"));
generator_tasks.insert(generator_task->thread_task_id, generator_task);
generator_task_mutex.unlock();
}
void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
NavMeshGeneratorTask3D *generator_task = static_cast<NavMeshGeneratorTask3D *>(p_arg);
generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data);
generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED;
}
void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children) { void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node); generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node); generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
@ -503,8 +612,8 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
return; return;
} }
const Vector<float> vertices = p_source_geometry_data->get_vertices(); const Vector<float> &vertices = p_source_geometry_data->get_vertices();
const Vector<int> indices = p_source_geometry_data->get_indices(); const Vector<int> &indices = p_source_geometry_data->get_indices();
if (vertices.size() < 3 || indices.size() < 3) { if (vertices.size() < 3 || indices.size() < 3) {
return; return;

View File

@ -34,6 +34,7 @@
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
#include "core/object/class_db.h" #include "core/object/class_db.h"
#include "core/object/worker_thread_pool.h"
#include "modules/modules_enabled.gen.h" // For csg, gridmap. #include "modules/modules_enabled.gen.h" // For csg, gridmap.
class Node; class Node;
@ -44,6 +45,31 @@ class NavMeshGenerator3D : public Object {
static NavMeshGenerator3D *singleton; static NavMeshGenerator3D *singleton;
static Mutex baking_navmesh_mutex; static Mutex baking_navmesh_mutex;
static Mutex generator_task_mutex;
static bool use_threads;
static bool baking_use_multiple_threads;
static bool baking_use_high_priority_threads;
struct NavMeshGeneratorTask3D {
enum TaskStatus {
BAKING_STARTED,
BAKING_FINISHED,
BAKING_FAILED,
CALLBACK_DISPATCHED,
CALLBACK_FAILED,
};
Ref<NavigationMesh> navigation_mesh;
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
Callable callback;
WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID;
NavMeshGeneratorTask3D::TaskStatus status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
};
static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> generator_tasks;
static void generator_thread_bake(void *p_arg);
static HashSet<Ref<NavigationMesh>> baking_navmeshes; static HashSet<Ref<NavigationMesh>> baking_navmeshes;
@ -66,11 +92,13 @@ class NavMeshGenerator3D : public Object {
public: public:
static NavMeshGenerator3D *get_singleton(); static NavMeshGenerator3D *get_singleton();
static void sync();
static void cleanup(); static void cleanup();
static void finish(); static void finish();
static void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); static void parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()); static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
NavMeshGenerator3D(); NavMeshGenerator3D();
~NavMeshGenerator3D(); ~NavMeshGenerator3D();

View File

@ -236,62 +236,29 @@ RID NavigationRegion3D::get_navigation_map() const {
return RID(); return RID();
} }
struct BakeThreadsArgs {
NavigationRegion3D *nav_region = nullptr;
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
};
void _bake_navigation_mesh(void *p_user_data) {
BakeThreadsArgs *args = static_cast<BakeThreadsArgs *>(p_user_data);
if (args->nav_region->get_navigation_mesh().is_valid()) {
Ref<NavigationMesh> nav_mesh = args->nav_region->get_navigation_mesh();
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data = args->source_geometry_data;
NavigationServer3D::get_singleton()->bake_from_source_geometry_data(nav_mesh, source_geometry_data);
if (!Thread::is_main_thread()) {
args->nav_region->call_deferred(SNAME("_bake_finished"), nav_mesh);
} else {
args->nav_region->_bake_finished(nav_mesh);
}
memdelete(args);
} else {
ERR_PRINT("Can't bake the navigation mesh if the `NavigationMesh` resource doesn't exist");
if (!Thread::is_main_thread()) {
args->nav_region->call_deferred(SNAME("_bake_finished"), Ref<NavigationMesh>());
} else {
args->nav_region->_bake_finished(Ref<NavigationMesh>());
}
memdelete(args);
}
}
void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) { void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
ERR_FAIL_COND_MSG(!navigation_mesh.is_valid(), "Baking the navigation mesh requires a valid `NavigationMesh` resource."); ERR_FAIL_COND_MSG(!navigation_mesh.is_valid(), "Baking the navigation mesh requires a valid `NavigationMesh` resource.");
ERR_FAIL_COND_MSG(bake_thread.is_started(), "Unable to start another bake request. The navigation mesh bake thread is already baking a navigation mesh.");
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data; Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
source_geometry_data.instantiate(); source_geometry_data.instantiate();
NavigationServer3D::get_singleton()->parse_source_geometry_data(navigation_mesh, source_geometry_data, this); NavigationServer3D::get_singleton()->parse_source_geometry_data(navigation_mesh, source_geometry_data, this);
BakeThreadsArgs *args = memnew(BakeThreadsArgs);
args->nav_region = this;
args->source_geometry_data = source_geometry_data;
if (p_on_thread) { if (p_on_thread) {
bake_thread.start(_bake_navigation_mesh, args); NavigationServer3D::get_singleton()->bake_from_source_geometry_data_async(navigation_mesh, source_geometry_data, callable_mp(this, &NavigationRegion3D::_bake_finished).bind(navigation_mesh));
} else { } else {
_bake_navigation_mesh(args); NavigationServer3D::get_singleton()->bake_from_source_geometry_data(navigation_mesh, source_geometry_data, callable_mp(this, &NavigationRegion3D::_bake_finished).bind(navigation_mesh));
} }
} }
void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_nav_mesh) { void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_navigation_mesh) {
set_navigation_mesh(p_nav_mesh); if (!Thread::is_main_thread()) {
if (bake_thread.is_started()) { call_deferred(SNAME("_bake_finished"), p_navigation_mesh);
bake_thread.wait_to_finish(); return;
} }
set_navigation_mesh(p_navigation_mesh);
emit_signal(SNAME("bake_finished")); emit_signal(SNAME("bake_finished"));
} }
@ -452,10 +419,6 @@ NavigationRegion3D::NavigationRegion3D() {
} }
NavigationRegion3D::~NavigationRegion3D() { NavigationRegion3D::~NavigationRegion3D() {
if (bake_thread.is_started()) {
bake_thread.wait_to_finish();
}
if (navigation_mesh.is_valid()) { if (navigation_mesh.is_valid()) {
navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
} }

View File

@ -49,8 +49,6 @@ class NavigationRegion3D : public Node3D {
Transform3D current_global_transform; Transform3D current_global_transform;
Thread bake_thread;
void _navigation_mesh_changed(); void _navigation_mesh_changed();
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED

View File

@ -155,6 +155,7 @@ void NavigationServer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_mesh", "source_geometry_data", "root_node", "callback"), &NavigationServer3D::parse_source_geometry_data, DEFVAL(Callable())); ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_mesh", "source_geometry_data", "root_node", "callback"), &NavigationServer3D::parse_source_geometry_data, DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_mesh", "source_geometry_data", "callback"), &NavigationServer3D::bake_from_source_geometry_data, DEFVAL(Callable())); ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_mesh", "source_geometry_data", "callback"), &NavigationServer3D::bake_from_source_geometry_data, DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data_async", "navigation_mesh", "source_geometry_data", "callback"), &NavigationServer3D::bake_from_source_geometry_data_async, DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free); ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free);
@ -204,6 +205,9 @@ NavigationServer3D::NavigationServer3D() {
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true); GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true); GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true);
GLOBAL_DEF("navigation/baking/thread_model/baking_use_multiple_threads", true);
GLOBAL_DEF("navigation/baking/thread_model/baking_use_high_priority_threads", true);
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
debug_navigation_edge_connection_color = GLOBAL_DEF("debug/shapes/navigation/edge_connection_color", Color(1.0, 0.0, 1.0, 1.0)); debug_navigation_edge_connection_color = GLOBAL_DEF("debug/shapes/navigation/edge_connection_color", Color(1.0, 0.0, 1.0, 1.0));
debug_navigation_geometry_edge_color = GLOBAL_DEF("debug/shapes/navigation/geometry_edge_color", Color(0.5, 1.0, 1.0, 1.0)); debug_navigation_geometry_edge_color = GLOBAL_DEF("debug/shapes/navigation/geometry_edge_color", Color(0.5, 1.0, 1.0, 1.0));

View File

@ -309,8 +309,9 @@ public:
virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const = 0; virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const = 0;
virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0; virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0;
virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0; virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
NavigationServer3D(); NavigationServer3D();
~NavigationServer3D() override; ~NavigationServer3D() override;

View File

@ -145,8 +145,9 @@ public:
void obstacle_set_position(RID p_obstacle, Vector3 p_position) override {} void obstacle_set_position(RID p_obstacle, Vector3 p_position) override {}
void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override {} void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override {}
void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {} void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {}
void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {} void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {}
void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {} void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
void free(RID p_object) override {} void free(RID p_object) override {}
void set_active(bool p_active) override {} void set_active(bool p_active) override {}
void process(real_t delta_time) override {} void process(real_t delta_time) override {}