-
Notifications
You must be signed in to change notification settings - Fork 92
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
Thread safety #125
Thread safety #125
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hoping there's a simpler way to get thread safety for MeshID2Original
. Let's not be too picky about breaking changes; I doubt anyone has used that feature yet anyway.
Actually regarding |
I suppose another option, which I think is in the same vein, is to just add an |
Then I think the only thing you'd need for thread safety is an atomic to increment the next |
Great, so when we copy A -> B -> C, C's |
Sorry I was busy with finals recently. I have a bit trouble understanding the purpose of Lines 67 to 72 in a7791d0
It seems that the original mesh ID used here is the immediate predecessor of the copy? i.e. when we copy A -> B -> C, If we follow this behavior, we should make C's Also, with the lazy boolean operation feature, the underlying copy constructor of the |
No problem at all! I got halfway through fixing the bug you found and then got COVID. Hoping to get back to it soon. You actually had it right before, original points all three way back to the first mesh, unless set as original is called. You get a new mesh id any time the mesh is copied, which includes booleans. It's like an instance id. Perhaps I should have named it that. |
Sorry to hear that, hope that you can recover soon. Regarding the mesh ID problem, I'm still not sure what is the purpose for unique ID for each copy. It seems to me that the major usage of this ID is to find the original mesh and apply the properties of the original mesh to the copy, which does not need a unique ID for each copy. |
Honestly, that's a good point. I had some reasons, but they may have been superceded by the set as original function. I was thinking it would help with finding the intersection between copies, but that can probably be deduced from which verts are original. It's been hard to explain on the documentation, which is probably reason enough to remove it. |
Great, I will just rename |
Is the It seems that if we have to keep the diff --git a/manifold/include/manifold.h b/manifold/include/manifold.h
index 16e0e0b..b101c34 100644
--- a/manifold/include/manifold.h
+++ b/manifold/include/manifold.h
@@ -105,7 +105,6 @@ class Manifold {
MeshRelation GetMeshRelation() const;
std::vector<int> GetMeshIDs() const;
int SetAsOriginal();
- static std::vector<int> MeshID2Original();
///@}
/** @name Modification
@@ -164,4 +163,4 @@ class Manifold {
static float circularEdgeLength_;
};
/** @} */
-} // namespace manifold
\ No newline at end of file
+} // namespace manifold
diff --git a/manifold/src/boolean_result.cpp b/manifold/src/boolean_result.cpp
index 807727f..6d302df 100644
--- a/manifold/src/boolean_result.cpp
+++ b/manifold/src/boolean_result.cpp
@@ -442,6 +442,9 @@ struct CreateBarycentric {
const BaryRef oldRef = halfedgeRef.PQ == 0 ? triBaryP[tri] : triBaryQ[tri];
faceRef[halfedgeR.face] = oldRef;
+ if (halfedgeRef.PQ != 0) {
+ faceRef[halfedgeR.face].meshID |= 1 << 30;
+ }
if (halfedgeR.startVert < firstNewVert) { // retained vert
int i = halfedgeRef.vert;
@@ -674,10 +677,18 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
Timer simplify;
simplify.Start();
- outR.DuplicateMeshIDs();
+ outR.meshRelation_.triBary.Dump();
outR.SimplifyTopology();
+ thrust::for_each(
+ thrust::device,
+ outR.meshRelation_.triBary.begin(),
+ outR.meshRelation_.triBary.end(),
+ []__host__ __device__(BaryRef& b) {
+ b.meshID &= ~(1 << 30);
+ });
+
simplify.Stop();
Timer sort;
sort.Start();
diff --git a/manifold/src/constructors.cpp b/manifold/src/constructors.cpp
index e0be155..c71c339 100644
--- a/manifold/src/constructors.cpp
+++ b/manifold/src/constructors.cpp
@@ -426,7 +426,6 @@ Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
nextBary += impl.meshRelation_.barycentric.size();
}
- combined.DuplicateMeshIDs();
combined.Finish();
return out;
}
diff --git a/manifold/src/impl.cpp b/manifold/src/impl.cpp
index d6ca915..a188e52 100644
--- a/manifold/src/impl.cpp
+++ b/manifold/src/impl.cpp
@@ -281,7 +281,7 @@ struct EdgeBox {
namespace manifold {
-std::vector<int> Manifold::Impl::meshID2Original_;
+std::atomic<int> Manifold::Impl::meshIDCounter_(1);
/**
* Create a manifold from an input triangle Mesh. Will throw if the Mesh is not
@@ -355,22 +355,6 @@ Manifold::Impl::Impl(Shape shape) {
InitializeNewReference();
}
-/**
- * When a manifold is copied, it is given a new unique set of mesh relation IDs,
- * identifying a particular instance of a copied input mesh. The original mesh
- * ID can be found using the meshID2Original mapping.
- */
-void Manifold::Impl::DuplicateMeshIDs() {
- std::map<int, int> old2new;
- for (BaryRef& ref : meshRelation_.triBary) {
- if (old2new.find(ref.meshID) == old2new.end()) {
- old2new[ref.meshID] = meshID2Original_.size();
- meshID2Original_.push_back(meshID2Original_[ref.meshID]);
- }
- ref.meshID = old2new[ref.meshID];
- }
-}
-
void Manifold::Impl::ReinitializeReference(int meshID) {
thrust::for_each_n(thrust::device, zip(meshRelation_.triBary.begin(), countAt(0)), NumTri(),
InitializeBaryRef({meshID, halfedge_.cptrD()}));
@@ -381,8 +365,7 @@ int Manifold::Impl::InitializeNewReference(
const std::vector<float>& properties,
const std::vector<float>& propertyTolerance) {
meshRelation_.triBary.resize(NumTri());
- const int nextMeshID = meshID2Original_.size();
- meshID2Original_.push_back(nextMeshID);
+ const int nextMeshID = meshIDCounter_.fetch_add(1);
ReinitializeReference(nextMeshID);
const int numProps = propertyTolerance.size();
diff --git a/manifold/src/impl.h b/manifold/src/impl.h
index 27c21b1..def5e66 100644
--- a/manifold/src/impl.h
+++ b/manifold/src/impl.h
@@ -40,7 +40,7 @@ struct Manifold::Impl {
Collider collider_;
glm::mat4x3 transform_ = glm::mat4x3(1.0f);
- static std::vector<int> meshID2Original_;
+ static std::atomic<int> meshIDCounter_;
Impl() {}
enum class Shape { TETRAHEDRON, CUBE, OCTAHEDRON };
@@ -56,7 +56,6 @@ struct Manifold::Impl {
const std::vector<float>& properties = std::vector<float>(),
const std::vector<float>& propertyTolerance = std::vector<float>());
- void DuplicateMeshIDs();
void ReinitializeReference(int meshID = -1);
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
void CreateAndFixHalfedges(const VecDH<glm::ivec3>& triVerts);
diff --git a/manifold/src/manifold.cpp b/manifold/src/manifold.cpp
index 642d2ac..dc04011 100644
--- a/manifold/src/manifold.cpp
+++ b/manifold/src/manifold.cpp
@@ -62,14 +62,11 @@ Manifold::~Manifold() = default;
Manifold::Manifold(Manifold&&) noexcept = default;
Manifold& Manifold::operator=(Manifold&&) noexcept = default;
-Manifold::Manifold(const Manifold& other) : pImpl_(new Impl(*other.pImpl_)) {
- pImpl_->DuplicateMeshIDs();
-}
+Manifold::Manifold(const Manifold& other) : pImpl_(new Impl(*other.pImpl_)) {}
Manifold& Manifold::operator=(const Manifold& other) {
if (this != &other) {
pImpl_.reset(new Impl(*other.pImpl_));
- pImpl_->DuplicateMeshIDs();
}
return *this;
}
@@ -320,15 +317,6 @@ int Manifold::SetAsOriginal() {
return meshID;
}
-/**
- * Returns a vector that maps a given unique MeshID to the MeshID of the
- * original Mesh it came from, to easily identify separate copies of the same
- * thing.
- */
-std::vector<int> Manifold::MeshID2Original() {
- return Manifold::Impl::meshID2Original_;
-}
-
/**
* Should always be true. Also checks saneness of the internal data structures.
*/
diff --git a/manifold/src/sort.cpp b/manifold/src/sort.cpp
index 7a0a67a..69541c6 100644
--- a/manifold/src/sort.cpp
+++ b/manifold/src/sort.cpp
@@ -315,7 +315,6 @@ void Manifold::Impl::GatherFaces(const Impl& old,
old.meshRelation_.triBary.begin(),
meshRelation_.triBary.begin());
meshRelation_.barycentric = old.meshRelation_.barycentric;
- DuplicateMeshIDs();
if (old.faceNormal_.size() == old.NumTri()) {
faceNormal_.resize(numTri);
diff --git a/test/mesh_test.cpp b/test/mesh_test.cpp
index 7fc0b7e..8213488 100644
--- a/test/mesh_test.cpp
+++ b/test/mesh_test.cpp
@@ -63,12 +63,9 @@ void Related(const Manifold& out, const std::vector<Mesh>& input,
const std::map<int, int>& meshID2idx) {
Mesh output = out.GetMesh();
MeshRelation relation = out.GetMeshRelation();
- std::vector<int> meshID2Original = Manifold::MeshID2Original();
for (int tri = 0; tri < out.NumTri(); ++tri) {
int meshID = relation.triBary[tri].meshID;
- int meshIdx = meshID2idx.find(meshID) != meshID2idx.end()
- ? meshID2idx.at(meshID)
- : meshID2idx.at(meshID2Original[meshID]);
+ int meshIdx = meshID2idx.at(meshID);
ASSERT_LT(meshIdx, input.size());
const Mesh& inMesh = input[meshIdx];
int inTri = relation.triBary[tri].tri;
@@ -869,4 +866,4 @@ TEST(Boolean, DISABLED_Cylinders) {
EXPECT_TRUE(m1.IsManifold());
EXPECT_TRUE(m1.MatchesTriNormals());
EXPECT_LE(m1.NumDegenerateTris(), 12);
-}
\ No newline at end of file
+} |
Removed static vector for thread safety. Accessing the original mesh ID can now be done by `originalID[meshID]`, where `originalID` is a field of `MeshRelation`. Note that `meshID` will not be updated when a manifold is cloned. Instead, it is only used to identify triangles coming from different manifolds when doing boolean operations/compose.
Disabled most of the importer/exporter formats by default, for faster development and CI. Note that you may need to CMakeCache.txt for this to take effect, as the options might be cached.
... at the expense of the internal code becoming a bit less intuitive. The meshID in the internal API is purely internal, usually starting from 0, which is used for differentiating between triangles coming from different meshes. originalID can map this internal meshID to the external meshID, and such mapping is automatically done when calling `GetMeshRelation`, so users don't have to care about this mapping. As a bonus, we don't need to deal with atomics when manipulating meshIDs internally: we can use whatever IDs we want as long as the mapping is preserved and distinct for triangles coming from different meshes. I also made the meshID modification code run in the device, which should be faster for larger meshes.
Final changes: The meshID in the internal API is purely internal, usually starting from As a bonus, we don't need to deal with atomics when manipulating meshIDs |
Yes, you are correct, sorry I forgot to mention that issue regarding edge collapse. And yes, I like your idea of making that an internal detail to simplify the API. I'm still feeling not great, so I can't promise when I'll do a proper code review. Hopefully soon. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great, thanks!
option(ASSIMP_INSTALL FALSE) | ||
option(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT FALSE) | ||
option(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT FALSE) | ||
foreach(FMT OBJ;PLY;STL;GLTF) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
manifold/src/boolean_result.cpp
Outdated
|
||
thrust::for_each(thrust::device, outR.meshRelation_.triBary.begin(), | ||
outR.meshRelation_.triBary.end(), | ||
[=] __host__ __device__(BaryRef &b) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to get used to using these device lambdas, at least for simple functions like this. Would probably clean up the code a lot. When I started, I couldn't get these to work.
@@ -105,7 +105,6 @@ class Manifold { | |||
MeshRelation GetMeshRelation() const; | |||
std::vector<int> GetMeshIDs() const; | |||
int SetAsOriginal(); | |||
static std::vector<int> MeshID2Original(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
for (int tri = 0; tri < out.NumTri(); ++tri) { | ||
int meshID = relation.triBary[tri].meshID; | ||
int meshIdx = meshID2idx.find(meshID) != meshID2idx.end() | ||
? meshID2idx.at(meshID) | ||
: meshID2idx.at(meshID2Original[meshID]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplifying the test is a good indication we're moving in the right direction on API shape. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, thanks for helping to simplify the API here!
Revisiting this after (mostly) finished the lazy boolean implementation. Keeping the meshIDs from different meshes distinct can fix the collapseEdge problem mentioned above, but it also prohibits reducing the set of vertices after boolean operations (e.g. when you union n cubes together as a larger cube, the number of vertices will grow to n*8). Perhaps we should try to use the originalID instead and see how to fix the issue regarding edge collapse. Do you have any documentation about the simplify topology code that I can have a look at? |
Thread safety
meshID2Original_
to make manifold copy constructors thread safe, as users may copy manifolds concurrently (I guess gtest do run tests concurrently?). AallMeshID
cache is added to cache all mesh IDs referenced in the manifoldpImpl_
, so we just need to iterate over this set while holding the lock tomeshID2Original_
, instead of having to iterate over the entiretriBary
vector while holding the lock.atomicAdd
to use c++ atomics instead of OpenMP pragma.Todo: Is there a way to wrap the vector with a mutex? So we can return
meshID2Original_
protected by a mutex in theMeshID2Original
function.