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

Add multi-threaded NavMesh baking to NavigationServer #79972

Merged
merged 1 commit into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
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
9 changes: 9 additions & 0 deletions doc/classes/NavigationServer3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</description>
</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">
<return type="void" />
<param index="0" name="rid" type="RID" />
Expand Down
6 changes: 6 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,12 @@
<member name="navigation/avoidance/thread_model/avoidance_use_multiple_threads" type="bool" setter="" getter="" default="true">
If enabled the avoidance calculations use multiple threads.
</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">
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>
Expand Down
34 changes: 22 additions & 12 deletions modules/navigation/godot_navigation_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions modules/navigation/godot_navigation_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,9 @@ class GodotNavigationServer : public NavigationServer3D {
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);

Expand Down
125 changes: 117 additions & 8 deletions modules/navigation/nav_mesh_generator_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand All @@ -71,23 +77,75 @@ 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();
}

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);
Expand All @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 30 additions & 2 deletions modules/navigation/nav_mesh_generator_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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();
Expand Down
Loading
Loading