Skip to content

Commit

Permalink
Fix thread use causing navigation mesh data corruption
Browse files Browse the repository at this point in the history
Fixes navigation mesh data corruption caused by thread use that changed vertices or polygons while the navigation mesh was processed, e.g. by server sync or baking.
  • Loading branch information
smix8 committed Jun 21, 2024
1 parent 6fb3b72 commit fd727ab
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 39 deletions.
2 changes: 1 addition & 1 deletion modules/navigation/3d/godot_navigation_server_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL(region);

region->set_mesh(p_navigation_mesh);
region->set_navigation_mesh(p_navigation_mesh);
}

#ifndef DISABLE_DEPRECATED
Expand Down
7 changes: 4 additions & 3 deletions modules/navigation/3d/nav_mesh_generator_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
bake_state = "Converting to native navigation mesh..."; // step #10

Vector<Vector3> nav_vertices;
Vector<Vector<int>> nav_polygons;

HashMap<Vector3, int> recast_vertex_to_native_index;
LocalVector<int> recast_index_to_native_index;
Expand All @@ -912,8 +913,6 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
recast_index_to_native_index[i] = *existing_index_ptr;
}
}
p_navigation_mesh->set_vertices(nav_vertices);
p_navigation_mesh->clear_polygons();

for (int i = 0; i < detail_mesh->nmeshes; i++) {
const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4];
Expand All @@ -933,10 +932,12 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
nav_indices.write[1] = recast_index_to_native_index[index2];
nav_indices.write[2] = recast_index_to_native_index[index3];

p_navigation_mesh->add_polygon(nav_indices);
nav_polygons.push_back(nav_indices);
}
}

p_navigation_mesh->set_data(nav_vertices, nav_polygons);

bake_state = "Cleanup..."; // step #11

rcFreePolyMesh(poly_mesh);
Expand Down
55 changes: 33 additions & 22 deletions modules/navigation/nav_region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,34 @@ void NavRegion::set_transform(Transform3D p_transform) {
}
transform = p_transform;
polygons_dirty = true;

#ifdef DEBUG_ENABLED
if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) {
ERR_PRINT_ONCE("Attempted to update a navigation region transform rotated 90 degrees or more away from the current navigation map UP orientation.");
}
#endif // DEBUG_ENABLED
}

void NavRegion::set_mesh(Ref<NavigationMesh> p_mesh) {
mesh = p_mesh;
void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
#ifdef DEBUG_ENABLED
if (map && !Math::is_equal_approx(double(map->get_cell_size()), double(p_navigation_mesh->get_cell_size()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_size()), double(map->get_cell_size())));
}

if (map && !Math::is_equal_approx(double(map->get_cell_height()), double(p_navigation_mesh->get_cell_height()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_height()), double(map->get_cell_height())));
}
#endif // DEBUG_ENABLED

RWLockWrite write_lock(navmesh_rwlock);

pending_navmesh_vertices.clear();
pending_navmesh_polygons.clear();

if (p_navigation_mesh.is_valid()) {
p_navigation_mesh->get_data(pending_navmesh_vertices, pending_navmesh_polygons);
}

polygons_dirty = true;
}

Expand Down Expand Up @@ -202,33 +226,20 @@ void NavRegion::update_polygons() {
return;
}

if (mesh.is_null()) {
return;
}

#ifdef DEBUG_ENABLED
if (!Math::is_equal_approx(double(map->get_cell_size()), double(mesh->get_cell_size()))) {
ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_size()), double(map->get_cell_size())));
}

if (!Math::is_equal_approx(double(map->get_cell_height()), double(mesh->get_cell_height()))) {
ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_height()), double(map->get_cell_height())));
}
RWLockRead read_lock(navmesh_rwlock);

if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) {
ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to update a navigation region transform rotated 90 degrees or more away from the current navigation map UP orientation.");
if (pending_navmesh_vertices.is_empty() || pending_navmesh_polygons.is_empty()) {
return;
}
#endif // DEBUG_ENABLED

Vector<Vector3> vertices = mesh->get_vertices();
int len = vertices.size();
int len = pending_navmesh_vertices.size();
if (len == 0) {
return;
}

const Vector3 *vertices_r = vertices.ptr();
const Vector3 *vertices_r = pending_navmesh_vertices.ptr();

polygons.resize(mesh->get_polygon_count());
polygons.resize(pending_navmesh_polygons.size());

real_t _new_region_surface_area = 0.0;

Expand All @@ -238,7 +249,7 @@ void NavRegion::update_polygons() {
polygon.owner = this;
polygon.surface_area = 0.0;

Vector<int> navigation_mesh_polygon = mesh->get_polygon(navigation_mesh_polygon_index);
Vector<int> navigation_mesh_polygon = pending_navmesh_polygons[navigation_mesh_polygon_index];
navigation_mesh_polygon_index += 1;

int navigation_mesh_polygon_size = navigation_mesh_polygon.size();
Expand Down
11 changes: 6 additions & 5 deletions modules/navigation/nav_region.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
#include "nav_base.h"
#include "nav_utils.h"

#include "core/os/rw_lock.h"
#include "scene/resources/navigation_mesh.h"

class NavRegion : public NavBase {
NavMap *map = nullptr;
Transform3D transform;
Ref<NavigationMesh> mesh;
Vector<gd::Edge::Connection> connections;
bool enabled = true;

Expand All @@ -52,6 +52,10 @@ class NavRegion : public NavBase {

real_t surface_area = 0.0;

RWLock navmesh_rwlock;
Vector<Vector3> pending_navmesh_vertices;
Vector<Vector<int>> pending_navmesh_polygons;

public:
NavRegion() {
type = NavigationUtilities::PathSegmentType::PATH_SEGMENT_TYPE_REGION;
Expand Down Expand Up @@ -79,10 +83,7 @@ class NavRegion : public NavBase {
return transform;
}

void set_mesh(Ref<NavigationMesh> p_mesh);
const Ref<NavigationMesh> get_mesh() const {
return mesh;
}
void set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh);

Vector<gd::Edge::Connection> &get_connections() {
return connections;
Expand Down
45 changes: 37 additions & 8 deletions scene/resources/navigation_mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@
#endif // DEBUG_ENABLED

void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) {
RWLockWrite write_lock(rwlock);
ERR_FAIL_COND(p_mesh.is_null());

vertices = Vector<Vector3>();
clear_polygons();
polygons.clear();

for (int i = 0; i < p_mesh->get_surface_count(); i++) {
if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
Expand All @@ -61,13 +62,12 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) {
const int *r = iarr.ptr();

for (int j = 0; j < rlen; j += 3) {
Vector<int> vi;
vi.resize(3);
vi.write[0] = r[j + 0] + from;
vi.write[1] = r[j + 1] + from;
vi.write[2] = r[j + 2] + from;

add_polygon(vi);
Polygon polygon;
polygon.indices.resize(3);
polygon.indices.write[0] = r[j + 0] + from;
polygon.indices.write[1] = r[j + 1] + from;
polygon.indices.write[2] = r[j + 2] + from;
polygons.push_back(polygon);
}
}
}
Expand Down Expand Up @@ -304,15 +304,18 @@ Vector3 NavigationMesh::get_filter_baking_aabb_offset() const {
}

void NavigationMesh::set_vertices(const Vector<Vector3> &p_vertices) {
RWLockWrite write_lock(rwlock);
vertices = p_vertices;
notify_property_list_changed();
}

Vector<Vector3> NavigationMesh::get_vertices() const {
RWLockRead read_lock(rwlock);
return vertices;
}

void NavigationMesh::_set_polygons(const Array &p_array) {
RWLockWrite write_lock(rwlock);
polygons.resize(p_array.size());
for (int i = 0; i < p_array.size(); i++) {
polygons.write[i].indices = p_array[i];
Expand All @@ -321,6 +324,7 @@ void NavigationMesh::_set_polygons(const Array &p_array) {
}

Array NavigationMesh::_get_polygons() const {
RWLockRead read_lock(rwlock);
Array ret;
ret.resize(polygons.size());
for (int i = 0; i < ret.size(); i++) {
Expand All @@ -331,30 +335,53 @@ Array NavigationMesh::_get_polygons() const {
}

void NavigationMesh::add_polygon(const Vector<int> &p_polygon) {
RWLockWrite write_lock(rwlock);
Polygon polygon;
polygon.indices = p_polygon;
polygons.push_back(polygon);
notify_property_list_changed();
}

int NavigationMesh::get_polygon_count() const {
RWLockRead read_lock(rwlock);
return polygons.size();
}

Vector<int> NavigationMesh::get_polygon(int p_idx) {
RWLockRead read_lock(rwlock);
ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>());
return polygons[p_idx].indices;
}

void NavigationMesh::clear_polygons() {
RWLockWrite write_lock(rwlock);
polygons.clear();
}

void NavigationMesh::clear() {
RWLockWrite write_lock(rwlock);
polygons.clear();
vertices.clear();
}

void NavigationMesh::set_data(const Vector<Vector3> &p_vertices, const Vector<Vector<int>> &p_polygons) {
RWLockWrite write_lock(rwlock);
vertices = p_vertices;
polygons.resize(p_polygons.size());
for (int i = 0; i < p_polygons.size(); i++) {
polygons.write[i].indices = p_polygons[i];
}
}

void NavigationMesh::get_data(Vector<Vector3> &r_vertices, Vector<Vector<int>> &r_polygons) {
RWLockRead read_lock(rwlock);
r_vertices = vertices;
r_polygons.resize(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
r_polygons.write[i] = polygons[i].indices;
}
}

#ifdef DEBUG_ENABLED
Ref<ArrayMesh> NavigationMesh::get_debug_mesh() {
if (debug_mesh.is_valid()) {
Expand All @@ -372,6 +399,8 @@ Ref<ArrayMesh> NavigationMesh::get_debug_mesh() {
return debug_mesh;
}

RWLockRead read_lock(rwlock);

int polygon_count = get_polygon_count();

if (polygon_count < 1) {
Expand Down
5 changes: 5 additions & 0 deletions scene/resources/navigation_mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
#ifndef NAVIGATION_MESH_H
#define NAVIGATION_MESH_H

#include "core/os/rw_lock.h"
#include "scene/resources/mesh.h"

class NavigationMesh : public Resource {
GDCLASS(NavigationMesh, Resource);
RWLock rwlock;

Vector<Vector3> vertices;
struct Polygon {
Expand Down Expand Up @@ -195,6 +197,9 @@ class NavigationMesh : public Resource {

void clear();

void set_data(const Vector<Vector3> &p_vertices, const Vector<Vector<int>> &p_polygons);
void get_data(Vector<Vector3> &r_vertices, Vector<Vector<int>> &r_polygons);

#ifdef DEBUG_ENABLED
Ref<ArrayMesh> get_debug_mesh();
#endif // DEBUG_ENABLED
Expand Down

0 comments on commit fd727ab

Please sign in to comment.