diff --git a/src/OpenMesh/Core/Utils/PropertyManager.hh b/src/OpenMesh/Core/Utils/PropertyManager.hh index 9319184d..183a77f5 100644 --- a/src/OpenMesh/Core/Utils/PropertyManager.hh +++ b/src/OpenMesh/Core/Utils/PropertyManager.hh @@ -78,25 +78,15 @@ namespace OpenMesh { */ template class PropertyManager { -#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || __cplusplus > 199711L || defined(__GXX_EXPERIMENTAL_CXX0X__) - public: - PropertyManager(const PropertyManager&) = delete; - PropertyManager& operator=(const PropertyManager&) = delete; -#else - private: - /** - * Noncopyable because there aren't no straightforward copy semantics. - */ - PropertyManager(const PropertyManager&); - - /** - * Noncopyable because there aren't no straightforward copy semantics. - */ - PropertyManager& operator=(const PropertyManager&); -#endif public: + using Value = typename PROPTYPE::Value; + using value_type = typename PROPTYPE::value_type; + using Handle = typename PROPTYPE::Handle; + /** + * @deprecated Use a constructor without \p existing and check existance with hasProperty() instead. + * * Constructor. * * Throws an \p std::runtime_error if \p existing is true and @@ -110,22 +100,114 @@ class PropertyManager { * the instance merely acts as a convenience wrapper around an existing property with no * lifecycle management whatsoever. * - * @see PropertyManager::createIfNotExists, makePropertyManagerFromNew, - * makePropertyManagerFromExisting, makePropertyManagerFromExistingOrNew + * @see PropertyManager::getOrMakeProperty, PropertyManager::getProperty, + * PropertyManager::makeTemporaryProperty */ - PropertyManager(PolyConnectivity& mesh, const char *propname, bool existing = false) : mesh_(&mesh), retain_(existing), name_(propname) { + OM_DEPRECATED("Use the constructor without parameter 'existing' instead. Check for existance with hasProperty") + PropertyManager(PolyConnectivity& mesh, const char *propname, bool existing) : mesh_(mesh), retain_(existing), name_(propname) { if (existing) { - if (!mesh_->get_property_handle(prop_, propname)) { + if (!mesh_.get_property_handle(prop_, propname)) { std::ostringstream oss; oss << "Requested property handle \"" << propname << "\" does not exist."; throw std::runtime_error(oss.str()); } } else { - mesh_->add_property(prop_, propname); + mesh_.add_property(prop_, propname); } } - PropertyManager() : mesh_(0), retain_(false) { + /** + * Constructor. + * + * Asks for a property with name propname and creates one if none exists. Lifetime is not managed. + * + * @param mesh The mesh on which to create the property. + * @param propname The name of the property. + */ + PropertyManager(PolyConnectivity& mesh, const char *propname) : mesh_(mesh), retain_(true), name_(propname) { + if (!mesh_.get_property_handle(prop_, propname)) { + mesh_.add_property(prop_, propname); + } + } + + /** + * Constructor. + * + * Asks for a property with name propname and creates one if none exists. Lifetime is not managed. + * If the property is created it is initialized with \p initial_value + * + * @param mesh The mesh on which to create the property. + * @param initial_value + * @param propname The name of the property. + */ + PropertyManager(const Value& intial_value, PolyConnectivity& mesh, const char *propname) : mesh_(mesh), retain_(true), name_(propname) { + if (!mesh_.get_property_handle(prop_, propname)) { + mesh_.add_property(prop_, propname); + set_range(mesh_.all_elements(), intial_value); + } + } + + /** + * Constructor. + * + * Create an anonymous property. Lifetime is managed. + * + * @param mesh The mesh on which to create the property. + */ + PropertyManager(PolyConnectivity& mesh) : mesh_(mesh), retain_(false), name_("") { + mesh_.add_property(prop_, name_); + } + + /** + * Constructor. + * + * Create an anonymous property. Lifetime is managed. + * If the property is created it is initialized with \p initial_value + * + * @param mesh The mesh on which to create the property. + */ + PropertyManager(const Value& intial_value, PolyConnectivity& mesh) : mesh_(mesh), retain_(false), name_("") { + mesh_.add_property(prop_, name_); + set_range(mesh_.all_elements(), intial_value); + } + + /** + * Constructor. + * + * Create a wrapper around an existing property. Lifetime is not managed. + * + * @param mesh The mesh on which to create the property. + */ + PropertyManager(PolyConnectivity& mesh, PROPTYPE property_handle) : mesh_(mesh), prop_(property_handle), retain_(true), name_() { + } + + PropertyManager() = delete; + + PropertyManager(const PropertyManager& rhs) + : + mesh_(rhs.mesh_), + prop_(), + retain_(rhs.retain_), + name_(rhs.name_) + { + if (rhs.retain_) // named property -> create a property manager referring to the same + { + prop_ = rhs.prop_; + } + else // unnamed property -> create a property manager refering to a new property and copy the contents + { + mesh_.add_property(prop_, name_); + rhs.copy_to(rhs.mesh_.template all_elements(), *this, mesh_.all_elements()); + } + } + + PropertyManager& operator=(const PropertyManager& rhs) + { + if (&mesh_ == &rhs.mesh_ && prop_ == rhs.prop_) + ; // nothing to do + else + rhs.copy_to(rhs.mesh_.template all_elements(), *this, mesh_.all_elements()); + return *this; } ~PropertyManager() { @@ -177,21 +259,40 @@ class PropertyManager { /** * Move constructor. Transfers ownership (delete responsibility). */ - PropertyManager(PropertyManager &&rhs) : mesh_(rhs.mesh_), prop_(rhs.prop_), retain_(rhs.retain_), name_(rhs.name_) { - rhs.retain_ = true; + PropertyManager(PropertyManager &&rhs) + : + mesh_(rhs.mesh_), + prop_(rhs.prop_), + retain_(rhs.retain_), + name_(rhs.name_) + { + if (!rhs.retain_) + rhs.prop_.invalidate(); // only invalidate unnamed properties } /** * Move assignment. Transfers ownership (delete responsibility). */ - PropertyManager &operator=(PropertyManager &&rhs) { - if (&rhs != this) { - deleteProperty(); - mesh_ = rhs.mesh_; - prop_ = rhs.prop_; - retain_ = rhs.retain_; - name_ = rhs.name_; - rhs.retain_ = true; + PropertyManager& operator=(PropertyManager&& rhs) + { + if ((&mesh_ != &rhs.mesh_) || (prop_ != rhs.prop_)) + { + if (rhs.retain_) + { + // retained properties cannot be invalidated. Copy instead + rhs.copy_to(rhs.mesh_.template all_elements(), *this, mesh_.all_elements()); + } + else + { + // switch the data stored in the properties + std::swap(mesh_.property(prop_).data_vector(), rhs.mesh_.property(rhs.prop_).data_vector()); + // resize the property to the correct size + mesh_.property(prop_).resize(mesh_.n_elements()); + // remove the property from rhs + rhs.mesh_.remove_property(rhs.prop_); + // invalidate prop_ + rhs.prop_.invalidate(); + } } return *this; } @@ -204,10 +305,7 @@ class PropertyManager { * @see makePropertyManagerFromExistingOrNew */ static PropertyManager createIfNotExists(PolyConnectivity &mesh, const char *propname) { - PROPTYPE dummy_prop; - PropertyManager pm(mesh, propname, mesh.get_property_handle(dummy_prop, propname)); - pm.retain(); - return std::move(pm); + return PropertyManager(mesh, propname); } /** @@ -248,8 +346,8 @@ class PropertyManager { } PropertyManager duplicate(const char *clone_name) { - PropertyManager pm(*mesh_, clone_name, false); - pm.mesh_->property(pm.prop_) = mesh_->property(prop_); + PropertyManager pm(mesh_, clone_name, false); + pm.mesh_.property(pm.prop_) = mesh_.property(prop_); return pm; } @@ -328,21 +426,11 @@ class PropertyManager { Proxy duplicate(const char *clone_name) { PropertyManager pm(*mesh_, clone_name, false); - pm.mesh_->property(pm.prop_) = mesh_->property(prop_); + pm.mesh_.property(pm.prop_) = mesh_.property(prop_); return (Proxy)pm; } #endif - /** - * \brief Disable lifecycle management for this property. - * - * If this method is called, the encapsulated property will not be deleted - * upon destruction of the PropertyManager instance. - */ - inline void retain(bool doRetain = true) { - retain_ = doRetain; - } - /** * Access the value of the encapsulated mesh property. * @@ -356,7 +444,7 @@ class PropertyManager { * @note This method is only used for mesh properties. */ typename PROPTYPE::reference& operator*() { - return mesh_->mproperty(prop_)[0]; + return mesh_.mproperty(prop_)[0]; } /** @@ -372,7 +460,7 @@ class PropertyManager { * @note This method is only used for mesh properties. */ typename PROPTYPE::const_reference& operator*() const { - return mesh_->mproperty(prop_)[0]; + return mesh_.mproperty(prop_)[0]; } /** @@ -384,7 +472,7 @@ class PropertyManager { */ template inline typename PROPTYPE::reference operator[] (const HandleType &handle) { - return mesh_->property(prop_, handle); + return mesh_.property(prop_, handle); } /** @@ -396,7 +484,7 @@ class PropertyManager { */ template inline typename PROPTYPE::const_reference operator[] (const HandleType &handle) const { - return mesh_->property(prop_, handle); + return mesh_.property(prop_, handle); } /** @@ -502,12 +590,12 @@ class PropertyManager { private: void deleteProperty() { - if (!retain_) - mesh_->remove_property(prop_); + if (!retain_ && prop_.is_valid()) + mesh_.remove_property(prop_); } private: - PolyConnectivity* mesh_; + PolyConnectivity& mesh_; PROPTYPE prop_; bool retain_; std::string name_; @@ -544,6 +632,27 @@ makeTemporaryProperty(PolyConnectivity &mesh, const char *propname = "") { return PropertyManager::type>(mesh, propname, false); } +///// shortcut or makeTemporaryrProperty +//template +//PropertyManager::type, MeshT> +//makeTemporaryVertexProperty(MeshT &mesh, const char *propname) { return makeTemporaryProperty(mesh, propname); } + +///// shortcut or makeTemporaryrProperty +//template +//PropertyManager::type, MeshT> +//makeTemporaryHalfedgeProperty(MeshT &mesh, const char *propname) { return makeTemporaryProperty(mesh, propname); } + +///// shortcut or makeTemporaryrProperty +//template +//PropertyManager::type, MeshT> +//makeTemporaryEdgeProperty(MeshT &mesh, const char *propname) { return makeTemporaryProperty(mesh, propname); } + +///// shortcut or makeTemporaryrProperty +//template +//PropertyManager::type, MeshT> +//makeTemporaryFaceProperty(MeshT &mesh, const char *propname) { return makeTemporaryProperty(mesh, propname); } + + /** @relates PropertyManager * * Obtains a handle to a named property. @@ -701,14 +810,14 @@ PropertyManager makePropertyManagerFromExistingOrNew(PolyConnectivity * * Intended for creating or accessing persistent properties. */ -template OM_DEPRECATED("Use getOrMakeProperty instead.") -PropertyManager makePropertyManagerFromExistingOrNew( - MeshT &mesh, const char *propname, +PropertyManager makePropertyManagerFromExistingOrNew( + PolyConnectivity &mesh, const char *propname, const ITERATOR_TYPE &begin, const ITERATOR_TYPE &end, const PROP_VALUE &init_value) { - return PropertyManager::createIfNotExists( + return PropertyManager::createIfNotExists( mesh, propname, begin, end, init_value); } @@ -734,5 +843,29 @@ PropertyManager makePropertyManagerFromExistingOrNew( mesh, propname, range.begin(), range.end(), init_value); } + +/** @relates PropertyManager + * Returns a convenience wrapper around the points property of a mesh. + */ +template +PropertyManager> +getPointsProperty(MeshT &mesh) { + return PropertyManager>(mesh, mesh.points_property_handle()); +} + + +template +using VProp = PropertyManager>; + +template +using HProp = PropertyManager>; + +template +using EProp = PropertyManager>; + +template +using FProp = PropertyManager>; + + } /* namespace OpenMesh */ #endif /* PROPERTYMANAGER_HH_ */ diff --git a/src/Unittests/unittests_propertymanager.cc b/src/Unittests/unittests_propertymanager.cc index ece51b6b..e5046194 100644 --- a/src/Unittests/unittests_propertymanager.cc +++ b/src/Unittests/unittests_propertymanager.cc @@ -230,4 +230,614 @@ TEST_F(OpenMeshPropertyManager, cpp11_persistent_and_non_owning_properties) { ASSERT_TRUE((OpenMesh::hasProperty(mesh_, prop_name))); } + +TEST_F(OpenMeshPropertyManager, property_copy_construction) { + for (int i = 0; i < 1000000; ++i) + mesh_.add_vertex(Mesh::Point()); + + // unnamed + { + auto prop1 = OpenMesh::VProp(mesh_); + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto prop2 = prop1; // prop1 and prop2 should be two different properties with the same content + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13); + } + + // named + { + auto prop1 = OpenMesh::VProp(mesh_, "ids"); + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto prop2 = prop1; // prop1 and prop2 should refere to the same property + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13); + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0); + } +} + +TEST_F(OpenMeshPropertyManager, property_move_construction) { + for (int i = 0; i < 1000000; ++i) + mesh_.add_vertex(Mesh::Point()); + + // unnamed + { + auto prop1 = OpenMesh::VProp(mesh_); + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto t_start = std::chrono::high_resolution_clock::now(); + auto prop2 = std::move(prop1); + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "move constructing property from temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_FALSE(prop1.isValid()) << "prop1 should have been invalidated"; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13); + } + + // named + { + auto prop1 = OpenMesh::VProp(mesh_, "ids"); + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto t_start = std::chrono::high_resolution_clock::now(); + auto prop2 = std::move(prop1); // prop1 and prop2 should refere to the same property + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "move constructing from named took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named properties cannot be invalidated"; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "property is not valid anymore"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "did not copy property correctly"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0); + } +} + + +TEST_F(OpenMeshPropertyManager, property_copying_same_mesh) { + + for (int i = 0; i < 1000000; ++i) + mesh_.add_vertex(Mesh::Point()); + + // unnamed to unnamed + { + auto prop1 = OpenMesh::VProp(3, mesh_); + auto prop2 = OpenMesh::VProp(0, mesh_); + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 3) << "Property not initialized correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property temporary to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Temporary property got destroyed"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 0) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } + + // unnamed to named + { + auto prop1 = OpenMesh::VProp(mesh_); + auto prop2 = OpenMesh::VProp(0, mesh_, "ids"); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property temporary to named took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Temporary property got destroyed"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(mesh_, "ids"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], -13) << "property with name 'ids' was not correctly changed"; + } + + // named to unnamed + { + auto prop1 = OpenMesh::VProp(mesh_, "ids2"); + auto prop2 = OpenMesh::VProp(mesh_); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property named to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + } + + // named to named (different names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids3"); + auto prop2 = OpenMesh::VProp(mesh_, "ids4"); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property named to named with different name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } + + // named to named (same names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids5"); + auto prop2 = OpenMesh::VProp(mesh_, "ids5"); + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; // this should be a no op + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property named to named with same name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + prop1.set_range(mesh_.vertices(), 42); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(mesh_, "ids5"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + } +} + + +TEST_F(OpenMeshPropertyManager, property_moving_same_mesh) { + + for (int i = 0; i < 1000000; ++i) + mesh_.add_vertex(Mesh::Point()); + + // unnamed to unnamed + { + auto prop1 = OpenMesh::VProp(mesh_); + auto prop2 = OpenMesh::VProp(mesh_); + prop2.set_range(mesh_.vertices(), 0); + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // this should be cheap + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property temporary to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_FALSE(prop1.isValid()) << "prop1 not invalidated after moving"; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } + + // unnamed to named + { + auto prop1 = OpenMesh::VProp(mesh_); + auto prop2 = OpenMesh::VProp(mesh_, "ids"); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property temporary to named took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_FALSE(prop1.isValid()) << "prop1 not invalidated after moving"; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(mesh_, "ids"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], -13) << "property with name 'ids' was not correctly changed"; + } + + // named to unnamed + { + auto prop1 = OpenMesh::VProp(mesh_, "ids2"); + auto prop2 = OpenMesh::VProp(mesh_); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // moving named properties will not invalidate the property and will copy the data + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property named to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named prop1 should not be invalidated by moving"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + } + + // named to named (different names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids3"); + auto prop2 = OpenMesh::VProp(mesh_, "ids4"); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // moving named properties will not invalidate the property and will copy the data + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property named to named with different name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named prop1 should not be invalidated by moving"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } + + // named to named (same names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids5"); + auto prop2 = OpenMesh::VProp(mesh_, "ids5"); + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // this should be a no op + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property named to named with same name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named prop1 should not be invalidated by moving"; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 0) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(mesh_, "ids5"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], 0) << "Property not copied correctly"; + } +} + + + +TEST_F(OpenMeshPropertyManager, property_copying_different_mesh) { + + for (int i = 0; i < 1000000; ++i) + mesh_.add_vertex(Mesh::Point()); + + auto copy = mesh_; + for (int i = 0; i < 10; ++i) + copy.add_vertex(Mesh::Point()); + + // unnamed to unnamed + { + auto prop1 = OpenMesh::VProp(3, mesh_); + auto prop2 = OpenMesh::VProp(0, copy); + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 3) << "Property not initialized correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property temporary to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Temporary property got destroyed"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 0) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_NO_FATAL_FAILURE(prop2[OpenMesh::VertexHandle(copy.n_vertices()-1)]) << "Property not correctly resized"; + } + + // unnamed to named + { + auto prop1 = OpenMesh::VProp(mesh_); + auto prop2 = OpenMesh::VProp(0, copy, "ids"); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property temporary to named took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Temporary property got destroyed"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(copy, "ids"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], -13) << "property with name 'ids' was not correctly changed"; + } + + // named to unnamed + { + auto prop1 = OpenMesh::VProp(mesh_, "ids2"); + auto prop2 = OpenMesh::VProp(copy); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property named to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + } + + // named to named (different names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids3"); + auto prop2 = OpenMesh::VProp(copy, "ids4"); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property named to named with different name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } + + // named to named (same names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids5"); + auto prop2 = OpenMesh::VProp(copy, "ids5"); + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = prop1; // this should be a no op + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "copying property named to named with same name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + prop1.set_range(mesh_.vertices(), 42); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(mesh_, "ids5"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + auto prop4 = OpenMesh::VProp(copy, "ids5"); + EXPECT_EQ(prop4[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } +} + + +TEST_F(OpenMeshPropertyManager, property_moving_different_mesh) { + + for (int i = 0; i < 1000000; ++i) + mesh_.add_vertex(Mesh::Point()); + + auto copy = mesh_; + for (int i = 0; i < 10; ++i) + copy.add_vertex(Mesh::Point()); + + // unnamed to unnamed + { + auto prop1 = OpenMesh::VProp(mesh_); + auto prop2 = OpenMesh::VProp(copy); + prop2.set_range(mesh_.vertices(), 0); + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // this should be cheap + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property temporary to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_FALSE(prop1.isValid()) << "prop1 not invalidated after moving"; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_NO_FATAL_FAILURE(prop2[OpenMesh::VertexHandle(copy.n_vertices()-1)]) << "Property not correctly resized"; + } + + // unnamed to named + { + auto prop1 = OpenMesh::VProp(mesh_); + auto prop2 = OpenMesh::VProp(copy, "ids"); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property temporary to named took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_FALSE(prop1.isValid()) << "prop1 not invalidated after moving"; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(copy, "ids"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], -13) << "property with name 'ids' was not correctly changed"; + } + + // named to unnamed + { + auto prop1 = OpenMesh::VProp(mesh_, "ids2"); + auto prop2 = OpenMesh::VProp(copy); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // moving named properties will not invalidate the property and will copy the data + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property named to temporary took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named prop1 should not be invalidated by moving"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + } + + // named to named (different names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids3"); + auto prop2 = OpenMesh::VProp(copy, "ids4"); + prop2.set_range(mesh_.vertices(), 0); + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], 0) << "Property not initialized correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // moving named properties will not invalidate the property and will copy the data + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property named to named with different name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named prop1 should not be invalidated by moving"; + + prop1.set_range(mesh_.vertices(), 0); + + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } + + // named to named (same names) + { + auto prop1 = OpenMesh::VProp(mesh_, "ids5"); + auto prop2 = OpenMesh::VProp(copy, "ids5"); + + for (auto vh : mesh_.vertices()) + prop1[vh] = vh.idx()*2-13; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto t_start = std::chrono::high_resolution_clock::now(); + prop2 = std::move(prop1); // should copy + auto t_end = std::chrono::high_resolution_clock::now(); + std::cout << "moving property named to named with same name took " << std::chrono::duration_cast(t_end-t_start).count() << "ms" << std::endl; + + EXPECT_TRUE(prop1.isValid()) << "named prop1 should not be invalidated by moving"; + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + prop1.set_range(mesh_.vertices(), 42); + + EXPECT_EQ(prop1[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + EXPECT_EQ(prop2[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + + auto prop3 = OpenMesh::VProp(mesh_, "ids5"); + EXPECT_EQ(prop3[OpenMesh::VertexHandle(0)], 42) << "Property not copied correctly"; + auto prop4 = OpenMesh::VProp(copy, "ids5"); + EXPECT_EQ(prop4[OpenMesh::VertexHandle(0)], -13) << "Property not copied correctly"; + } +} + + + }