diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 8f1aedda..7e536c84 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -220,6 +220,29 @@ void mjCMesh::CopyFromSpec() { plugin.instance = spec.plugin.instance; plugin.name = spec.plugin.name; plugin.instance_name = spec.plugin.instance_name; + + // clear precompiled asset. TODO: use asset cache + if (vert_) mju_free(vert_); + if (normal_) mju_free(normal_); + if (texcoord_) mju_free(texcoord_); + if (center_) mju_free(center_); + if (face_) mju_free(face_); + if (facenormal_) mju_free(facenormal_); + if (facetexcoord_) mju_free(facetexcoord_); + if (graph_) mju_free(graph_); + nvert_= 0; + nnormal_ = 0; + ntexcoord_ = 0; + nface_ = 0; + szgraph_ = 0; + vert_ = NULL; + normal_ = NULL; + center_ = NULL; + texcoord_ = NULL; + face_ = NULL; + facenormal_ = NULL; + facetexcoord_ = NULL; + graph_ = NULL; } @@ -2265,6 +2288,12 @@ void mjCFlex::CopyFromSpec() { vert = (mjDoubleVec)&vert_; texcoord = (mjFloatVec)&texcoord_; elem = (mjIntVec)&elem_; + + // clear precompiled asset. TODO: use asset cache + nedge = 0; + edge.clear(); + shell.clear(); + evpair.clear(); } diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 9f954991..054eb4db 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -100,7 +100,6 @@ mjCModel::mjCModel() { center_auto[0] = center_auto[1] = center_auto[2] = 0; #endif - nmocap = 0; nplugin = 0; //------------------------ private variables cameras.clear(); @@ -326,6 +325,7 @@ void mjCModel::Clear(void) { nB = 0; njmax = -1; nconmax = -1; + nmocap = 0; // pointer lists created by Compile bodies.clear(); @@ -2734,6 +2734,16 @@ static void warninghandler(const char* msg) { // compiler mjModel* mjCModel::Compile(const mjVFS* vfs) { + if (compiled) { + // clear kinematic tree + for (int i=0; isubtreedofs = 0; + } + mjCBody* world = bodies[0]; + Clear(); + bodies.push_back(world); + } + CopyFromSpec(); // The volatile keyword is necessary to prevent a possible memory leak due to @@ -2799,11 +2809,6 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { throw mjCError(0, "NaN test does not work for present compiler/options"); } - // check for repeated compilation - if (compiled) { - throw mjCError(0, "model already compiled"); - } - // check for joints in world body if (!bodies[0]->joints.empty()) { throw mjCError(0, "joint found in world body"); diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index fb4650a1..31bf9765 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -268,26 +270,31 @@ void mjCBoundingVolumeHierarchy::Set(mjtNum ipos_element[3], mjtNum iquat_elemen -void mjCBoundingVolumeHierarchy::AllocateBoundingVolumes(int nbvh) { - bvh_.resize(nbvh); +void mjCBoundingVolumeHierarchy::AllocateBoundingVolumes(int nleaf) { + nbvh = 0; + child.clear(); + nodeid.clear(); + level.clear(); + bvleaf_.clear(); + bvleaf_.resize(nleaf); } void mjCBoundingVolumeHierarchy::RemoveInactiveVolumes(int nmax) { - bvh_.erase(bvh_.begin() + nmax, bvh_.end()); + bvleaf_.erase(bvleaf_.begin() + nmax, bvleaf_.end()); } mjCBoundingVolume* mjCBoundingVolumeHierarchy::GetBoundingVolume(int id) { - return bvh_.data() + id; + return bvleaf_.data() + id; } // create bounding volume hierarchy void mjCBoundingVolumeHierarchy::CreateBVH() { - std::vector elements(bvh_.size()); - for (int i=0; i elements(bvleaf_.size()); + for (int i=0; itype==mjWRAP_CYLINDER) { + path[i]->type = mjWRAP_SPHERE; + } + } } @@ -5402,27 +5432,31 @@ mjCPlugin::mjCPlugin(mjCModel* _model) { void mjCPlugin::Compile(void) { const mjpPlugin* plugin = mjp_getPluginAtSlot(spec.plugin_slot); + // clear precompiled + flattened_attributes.clear(); + std::map> config_attribs_copy = config_attribs; + // concatenate all of the plugin's attribute values (as null-terminated strings) into // flattened_attributes, in the order declared in the mjpPlugin // each valid attribute found is appended to flattened_attributes and removed from xml_attributes for (int i = 0; i < plugin->nattribute; ++i) { std::string_view attr(plugin->attributes[i]); - auto it = config_attribs.find(attr); - if (it == config_attribs.end()) { + auto it = config_attribs_copy.find(attr); + if (it == config_attribs_copy.end()) { flattened_attributes.push_back('\0'); } else { auto original_size = flattened_attributes.size(); flattened_attributes.resize(original_size + it->second.size() + 1); std::memcpy(&flattened_attributes[original_size], it->second.c_str(), it->second.size() + 1); - config_attribs.erase(it); + config_attribs_copy.erase(it); } } // anything left in xml_attributes at this stage is not a valid attribute - if (!config_attribs.empty()) { + if (!config_attribs_copy.empty()) { std::string error = - "unrecognized attribute 'plugin:" + config_attribs.begin()->first + + "unrecognized attribute 'plugin:" + config_attribs_copy.begin()->first + "' for plugin " + std::string(plugin->name) + "'"; throw mjCError(parent, "%s", error.c_str()); } diff --git a/src/user/user_objects.h b/src/user/user_objects.h index da7e17e6..675eb8aa 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -130,14 +130,14 @@ class mjCBoundingVolumeHierarchy { // make bounding volume hierarchy void CreateBVH(void); void Set(mjtNum ipos_element[3], mjtNum iquat_element[4]); - void AllocateBoundingVolumes(int nbvh); + void AllocateBoundingVolumes(int nleaf); void RemoveInactiveVolumes(int nmax); mjCBoundingVolume* GetBoundingVolume(int id); private: int MakeBVH(std::vector& elements, int lev = 0); - std::vector bvh_; + std::vector bvleaf_; std::string name_; double ipos_[3]; double iquat_[4]; diff --git a/src/xml/xml.h b/src/xml/xml.h index 3afb5aab..a97d6a65 100644 --- a/src/xml/xml.h +++ b/src/xml/xml.h @@ -17,6 +17,7 @@ #include +#include #include #include "user/user_api.h" @@ -26,7 +27,7 @@ std::string mjWriteXML(mjmModel* model, char* error, int error_sz); // Main parser function -mjmModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz); +MJAPI mjmModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz); #endif // MUJOCO_SRC_XML_XML_H_ diff --git a/test/fixture.cc b/test/fixture.cc index c86a9c8a..dca1a280 100644 --- a/test/fixture.cc +++ b/test/fixture.cc @@ -207,7 +207,7 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2, // compare ints #define X(name) \ - if (m1->name != m2->name) {maxdif = 1.0; field = #name;} + if (m1->name != m2->name) {maxdif = m1->name - m2->name; field = #name;} MJMODEL_INTS #undef X if (maxdif > 0) return maxdif; diff --git a/test/user/CMakeLists.txt b/test/user/CMakeLists.txt index 0c824e96..66f8615a 100644 --- a/test/user/CMakeLists.txt +++ b/test/user/CMakeLists.txt @@ -18,7 +18,12 @@ target_link_libraries(user_model_test fixture gmock absl::str_format) mujoco_test(user_objects_test) target_link_libraries(user_objects_test fixture gmock) -mujoco_test(user_api_test) +mujoco_test( + user_api_test + PROPERTIES + ENVIRONMENT + "MUJOCO_PLUGIN_DIR=$" +) target_link_libraries(user_api_test fixture gmock) mujoco_test(user_flex_test) diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index 5bd73525..1c215760 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -14,16 +14,25 @@ // Tests for user/user_api.cc. +#include +#include +#include +#include #include #include +#include +#include #include "src/user/user_api.h" +#include "src/xml/xml.h" +#include "src/xml/xml_numeric_format.h" #include "test/fixture.h" namespace mujoco { namespace { using ::testing::HasSubstr; +using ::testing::NotNull; // ----------------------------- test set/get -------------------------------- @@ -53,5 +62,59 @@ TEST_F(MujocoTest, ReadWriteData) { mjm_deleteModel(model); } +// ------------------- test recompilation multiple files ---------------------- +TEST_F(PluginTest, RecompileCompare) { + // full precision float printing + FullFloatPrecision increase_precision; + + // loop over all xml files in data + std::vector paths = {GetTestDataFilePath("."), + GetModelPath(".")}; + std::string ext(".xml"); + for (auto const& path : paths) { + for (auto &p : std::filesystem::recursive_directory_iterator(path)) { + if (p.path().extension() == ext) { + std::string xml = p.path().string(); + + // if file is meant to fail, skip it + if (absl::StrContains(p.path().string(), "malformed_") || + absl::StrContains(p.path().string(), "touch_grid") || + absl::StrContains(p.path().string(), "cow") || + absl::StrContains(p.path().string(), "discardvisual")) { + continue; + } + + // load model + std::array error; + mjmModel* model = + mjParseXML(xml.c_str(), nullptr, error.data(), error.size()); + + // compile twice + mjModel* m_old = mjm_compileModel(model, nullptr); + mjModel* m_new = mjm_compileModel(model, nullptr); + + ASSERT_THAT(m_old, NotNull()) + << "Failed to compile " << xml << ": " << error.data(); + ASSERT_THAT(m_new, NotNull()) + << "Failed to recompile " << xml << ": " << error.data(); + + // compare and delete + std::string field = ""; + mjtNum result = CompareModel(m_old, m_new, field); + mjtNum tol = 0; + EXPECT_LE(result, tol) + << "Loaded and saved models are different!\n" + << "Affected file " << p.path().string() << '\n' + << "Different field: " << field << '\n'; + + // delete models + mjm_deleteModel(model); + mj_deleteModel(m_old); + mj_deleteModel(m_new); + } + } + } +} + } // namespace } // namespace mujoco