Add multi-threaded NavMesh baking to NavigationServer
Adds multi-threaded NavMesh baking to NavigationServer.
This commit is contained in:
@ -930,7 +930,7 @@ COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, 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
|
||||
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.");
|
||||
@ -942,26 +942,26 @@ void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh>
|
||||
#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
|
||||
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
|
||||
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());
|
||||
NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
|
||||
#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) {
|
||||
if (map_owner.owns(p_object)) {
|
||||
NavMap *map = map_owner.get_or_null(p_object);
|
||||
@ -1093,6 +1093,16 @@ void GodotNavigationServer::process(real_t p_delta_time) {
|
||||
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_agent_count = 0;
|
||||
int _new_pm_link_count = 0;
|
||||
|
||||
@ -228,8 +228,9 @@ public:
|
||||
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);
|
||||
|
||||
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 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 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(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);
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
|
||||
#include "nav_mesh_generator_3d.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/math/convex_hull.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
@ -62,7 +63,12 @@
|
||||
|
||||
NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr;
|
||||
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;
|
||||
HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks;
|
||||
|
||||
NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
|
||||
return singleton;
|
||||
@ -71,15 +77,67 @@ NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
|
||||
NavMeshGenerator3D::NavMeshGenerator3D() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
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() {
|
||||
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() {
|
||||
baking_navmesh_mutex.lock();
|
||||
generator_task_mutex.lock();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -87,7 +145,7 @@ void NavMeshGenerator3D::finish() {
|
||||
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(!p_navigation_mesh.is_valid());
|
||||
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_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();
|
||||
if (baking_navmeshes.has(p_navigation_mesh)) {
|
||||
baking_navmesh_mutex.unlock();
|
||||
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);
|
||||
|
||||
@ -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) {
|
||||
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);
|
||||
@ -503,8 +612,8 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<float> vertices = p_source_geometry_data->get_vertices();
|
||||
const Vector<int> indices = p_source_geometry_data->get_indices();
|
||||
const Vector<float> &vertices = p_source_geometry_data->get_vertices();
|
||||
const Vector<int> &indices = p_source_geometry_data->get_indices();
|
||||
|
||||
if (vertices.size() < 3 || indices.size() < 3) {
|
||||
return;
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/worker_thread_pool.h"
|
||||
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
|
||||
|
||||
class Node;
|
||||
@ -44,6 +45,31 @@ class NavMeshGenerator3D : public Object {
|
||||
static NavMeshGenerator3D *singleton;
|
||||
|
||||
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;
|
||||
|
||||
@ -66,11 +92,13 @@ class NavMeshGenerator3D : public Object {
|
||||
public:
|
||||
static NavMeshGenerator3D *get_singleton();
|
||||
|
||||
static void sync();
|
||||
static void cleanup();
|
||||
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 bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, 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, 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();
|
||||
|
||||
Reference in New Issue
Block a user