Add recompilation test to user_api_test.
PiperOrigin-RevId: 613192683 Change-Id: Ibb44e1c2d63ac0afb7b5f3a15dff26378deb6c82
This commit is contained in:
committed by
Copybara-Service
parent
a00f1b5f30
commit
9ac048e262
@@ -220,6 +220,29 @@ void mjCMesh::CopyFromSpec() {
|
|||||||
plugin.instance = spec.plugin.instance;
|
plugin.instance = spec.plugin.instance;
|
||||||
plugin.name = spec.plugin.name;
|
plugin.name = spec.plugin.name;
|
||||||
plugin.instance_name = spec.plugin.instance_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_;
|
vert = (mjDoubleVec)&vert_;
|
||||||
texcoord = (mjFloatVec)&texcoord_;
|
texcoord = (mjFloatVec)&texcoord_;
|
||||||
elem = (mjIntVec)&elem_;
|
elem = (mjIntVec)&elem_;
|
||||||
|
|
||||||
|
// clear precompiled asset. TODO: use asset cache
|
||||||
|
nedge = 0;
|
||||||
|
edge.clear();
|
||||||
|
shell.clear();
|
||||||
|
evpair.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -100,7 +100,6 @@ mjCModel::mjCModel() {
|
|||||||
center_auto[0] = center_auto[1] = center_auto[2] = 0;
|
center_auto[0] = center_auto[1] = center_auto[2] = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
nmocap = 0;
|
|
||||||
nplugin = 0;
|
nplugin = 0;
|
||||||
//------------------------ private variables
|
//------------------------ private variables
|
||||||
cameras.clear();
|
cameras.clear();
|
||||||
@@ -326,6 +325,7 @@ void mjCModel::Clear(void) {
|
|||||||
nB = 0;
|
nB = 0;
|
||||||
njmax = -1;
|
njmax = -1;
|
||||||
nconmax = -1;
|
nconmax = -1;
|
||||||
|
nmocap = 0;
|
||||||
|
|
||||||
// pointer lists created by Compile
|
// pointer lists created by Compile
|
||||||
bodies.clear();
|
bodies.clear();
|
||||||
@@ -2734,6 +2734,16 @@ static void warninghandler(const char* msg) {
|
|||||||
|
|
||||||
// compiler
|
// compiler
|
||||||
mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
||||||
|
if (compiled) {
|
||||||
|
// clear kinematic tree
|
||||||
|
for (int i=0; i<bodies.size(); i++) {
|
||||||
|
bodies[i]->subtreedofs = 0;
|
||||||
|
}
|
||||||
|
mjCBody* world = bodies[0];
|
||||||
|
Clear();
|
||||||
|
bodies.push_back(world);
|
||||||
|
}
|
||||||
|
|
||||||
CopyFromSpec();
|
CopyFromSpec();
|
||||||
|
|
||||||
// The volatile keyword is necessary to prevent a possible memory leak due to
|
// 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");
|
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
|
// check for joints in world body
|
||||||
if (!bodies[0]->joints.empty()) {
|
if (!bodies[0]->joints.empty()) {
|
||||||
throw mjCError(0, "joint found in world body");
|
throw mjCError(0, "joint found in world body");
|
||||||
|
|||||||
+46
-12
@@ -21,6 +21,8 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -268,26 +270,31 @@ void mjCBoundingVolumeHierarchy::Set(mjtNum ipos_element[3], mjtNum iquat_elemen
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
void mjCBoundingVolumeHierarchy::AllocateBoundingVolumes(int nbvh) {
|
void mjCBoundingVolumeHierarchy::AllocateBoundingVolumes(int nleaf) {
|
||||||
bvh_.resize(nbvh);
|
nbvh = 0;
|
||||||
|
child.clear();
|
||||||
|
nodeid.clear();
|
||||||
|
level.clear();
|
||||||
|
bvleaf_.clear();
|
||||||
|
bvleaf_.resize(nleaf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void mjCBoundingVolumeHierarchy::RemoveInactiveVolumes(int nmax) {
|
void mjCBoundingVolumeHierarchy::RemoveInactiveVolumes(int nmax) {
|
||||||
bvh_.erase(bvh_.begin() + nmax, bvh_.end());
|
bvleaf_.erase(bvleaf_.begin() + nmax, bvleaf_.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
mjCBoundingVolume* mjCBoundingVolumeHierarchy::GetBoundingVolume(int id) {
|
mjCBoundingVolume* mjCBoundingVolumeHierarchy::GetBoundingVolume(int id) {
|
||||||
return bvh_.data() + id;
|
return bvleaf_.data() + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// create bounding volume hierarchy
|
// create bounding volume hierarchy
|
||||||
void mjCBoundingVolumeHierarchy::CreateBVH() {
|
void mjCBoundingVolumeHierarchy::CreateBVH() {
|
||||||
std::vector<const mjCBoundingVolume*> elements(bvh_.size());
|
std::vector<const mjCBoundingVolume*> elements(bvleaf_.size());
|
||||||
for (int i=0; i<bvh_.size(); i++) {
|
for (int i=0; i<bvleaf_.size(); i++) {
|
||||||
elements[i] = bvh_.data() + i;
|
elements[i] = bvleaf_.data() + i;
|
||||||
}
|
}
|
||||||
MakeBVH(elements);
|
MakeBVH(elements);
|
||||||
}
|
}
|
||||||
@@ -2507,6 +2514,16 @@ void mjCHField::CopyFromSpec() {
|
|||||||
file = (mjString)&file_;
|
file = (mjString)&file_;
|
||||||
content_type = (mjString)&content_type_;
|
content_type = (mjString)&content_type_;
|
||||||
userdata = (mjFloatVec)&userdata_;
|
userdata = (mjFloatVec)&userdata_;
|
||||||
|
|
||||||
|
// clear precompiled asset. TODO: use asset cache
|
||||||
|
if (data) {
|
||||||
|
mju_free(data);
|
||||||
|
data = 0;
|
||||||
|
}
|
||||||
|
if (!file_.empty()) {
|
||||||
|
nrow = 0;
|
||||||
|
ncol = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2742,6 +2759,12 @@ void mjCTexture::CopyFromSpec() {
|
|||||||
file = (mjString)&file_;
|
file = (mjString)&file_;
|
||||||
content_type = (mjString)&content_type_;
|
content_type = (mjString)&content_type_;
|
||||||
cubefiles = (mjStringVec)&cubefiles_;
|
cubefiles = (mjStringVec)&cubefiles_;
|
||||||
|
|
||||||
|
// clear precompiled asset. TODO: use asset cache
|
||||||
|
if (rgb) {
|
||||||
|
mju_free(rgb);
|
||||||
|
rgb = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3912,6 +3935,13 @@ void mjCTendon::CopyFromSpec() {
|
|||||||
userdata_ = spec_userdata_;
|
userdata_ = spec_userdata_;
|
||||||
material = (mjString)&material_;
|
material = (mjString)&material_;
|
||||||
userdata = (mjDoubleVec)&userdata_;
|
userdata = (mjDoubleVec)&userdata_;
|
||||||
|
|
||||||
|
// clear precompiled
|
||||||
|
for (int i=0; i<path.size(); i++) {
|
||||||
|
if (path[i]->type==mjWRAP_CYLINDER) {
|
||||||
|
path[i]->type = mjWRAP_SPHERE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -5402,27 +5432,31 @@ mjCPlugin::mjCPlugin(mjCModel* _model) {
|
|||||||
void mjCPlugin::Compile(void) {
|
void mjCPlugin::Compile(void) {
|
||||||
const mjpPlugin* plugin = mjp_getPluginAtSlot(spec.plugin_slot);
|
const mjpPlugin* plugin = mjp_getPluginAtSlot(spec.plugin_slot);
|
||||||
|
|
||||||
|
// clear precompiled
|
||||||
|
flattened_attributes.clear();
|
||||||
|
std::map<std::string, std::string, std::less<>> config_attribs_copy = config_attribs;
|
||||||
|
|
||||||
// concatenate all of the plugin's attribute values (as null-terminated strings) into
|
// concatenate all of the plugin's attribute values (as null-terminated strings) into
|
||||||
// flattened_attributes, in the order declared in the mjpPlugin
|
// flattened_attributes, in the order declared in the mjpPlugin
|
||||||
// each valid attribute found is appended to flattened_attributes and removed from xml_attributes
|
// each valid attribute found is appended to flattened_attributes and removed from xml_attributes
|
||||||
for (int i = 0; i < plugin->nattribute; ++i) {
|
for (int i = 0; i < plugin->nattribute; ++i) {
|
||||||
std::string_view attr(plugin->attributes[i]);
|
std::string_view attr(plugin->attributes[i]);
|
||||||
auto it = config_attribs.find(attr);
|
auto it = config_attribs_copy.find(attr);
|
||||||
if (it == config_attribs.end()) {
|
if (it == config_attribs_copy.end()) {
|
||||||
flattened_attributes.push_back('\0');
|
flattened_attributes.push_back('\0');
|
||||||
} else {
|
} else {
|
||||||
auto original_size = flattened_attributes.size();
|
auto original_size = flattened_attributes.size();
|
||||||
flattened_attributes.resize(original_size + it->second.size() + 1);
|
flattened_attributes.resize(original_size + it->second.size() + 1);
|
||||||
std::memcpy(&flattened_attributes[original_size], it->second.c_str(),
|
std::memcpy(&flattened_attributes[original_size], it->second.c_str(),
|
||||||
it->second.size() + 1);
|
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
|
// 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 =
|
std::string error =
|
||||||
"unrecognized attribute 'plugin:" + config_attribs.begin()->first +
|
"unrecognized attribute 'plugin:" + config_attribs_copy.begin()->first +
|
||||||
"' for plugin " + std::string(plugin->name) + "'";
|
"' for plugin " + std::string(plugin->name) + "'";
|
||||||
throw mjCError(parent, "%s", error.c_str());
|
throw mjCError(parent, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,14 +130,14 @@ class mjCBoundingVolumeHierarchy {
|
|||||||
// make bounding volume hierarchy
|
// make bounding volume hierarchy
|
||||||
void CreateBVH(void);
|
void CreateBVH(void);
|
||||||
void Set(mjtNum ipos_element[3], mjtNum iquat_element[4]);
|
void Set(mjtNum ipos_element[3], mjtNum iquat_element[4]);
|
||||||
void AllocateBoundingVolumes(int nbvh);
|
void AllocateBoundingVolumes(int nleaf);
|
||||||
void RemoveInactiveVolumes(int nmax);
|
void RemoveInactiveVolumes(int nmax);
|
||||||
mjCBoundingVolume* GetBoundingVolume(int id);
|
mjCBoundingVolume* GetBoundingVolume(int id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int MakeBVH(std::vector<const mjCBoundingVolume*>& elements, int lev = 0);
|
int MakeBVH(std::vector<const mjCBoundingVolume*>& elements, int lev = 0);
|
||||||
|
|
||||||
std::vector<mjCBoundingVolume> bvh_;
|
std::vector<mjCBoundingVolume> bvleaf_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
double ipos_[3];
|
double ipos_[3];
|
||||||
double iquat_[4];
|
double iquat_[4];
|
||||||
|
|||||||
+2
-1
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <mujoco/mjexport.h>
|
||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include "user/user_api.h"
|
#include "user/user_api.h"
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
std::string mjWriteXML(mjmModel* model, char* error, int error_sz);
|
std::string mjWriteXML(mjmModel* model, char* error, int error_sz);
|
||||||
|
|
||||||
// Main parser function
|
// 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_
|
#endif // MUJOCO_SRC_XML_XML_H_
|
||||||
|
|||||||
+1
-1
@@ -207,7 +207,7 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2,
|
|||||||
|
|
||||||
// compare ints
|
// compare ints
|
||||||
#define X(name) \
|
#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
|
MJMODEL_INTS
|
||||||
#undef X
|
#undef X
|
||||||
if (maxdif > 0) return maxdif;
|
if (maxdif > 0) return maxdif;
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ target_link_libraries(user_model_test fixture gmock absl::str_format)
|
|||||||
mujoco_test(user_objects_test)
|
mujoco_test(user_objects_test)
|
||||||
target_link_libraries(user_objects_test fixture gmock)
|
target_link_libraries(user_objects_test fixture gmock)
|
||||||
|
|
||||||
mujoco_test(user_api_test)
|
mujoco_test(
|
||||||
|
user_api_test
|
||||||
|
PROPERTIES
|
||||||
|
ENVIRONMENT
|
||||||
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:elasticity>"
|
||||||
|
)
|
||||||
target_link_libraries(user_api_test fixture gmock)
|
target_link_libraries(user_api_test fixture gmock)
|
||||||
|
|
||||||
mujoco_test(user_flex_test)
|
mujoco_test(user_flex_test)
|
||||||
|
|||||||
@@ -14,16 +14,25 @@
|
|||||||
|
|
||||||
// Tests for user/user_api.cc.
|
// Tests for user/user_api.cc.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <absl/strings/match.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
#include "src/user/user_api.h"
|
#include "src/user/user_api.h"
|
||||||
|
#include "src/xml/xml.h"
|
||||||
|
#include "src/xml/xml_numeric_format.h"
|
||||||
#include "test/fixture.h"
|
#include "test/fixture.h"
|
||||||
|
|
||||||
namespace mujoco {
|
namespace mujoco {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ::testing::HasSubstr;
|
using ::testing::HasSubstr;
|
||||||
|
using ::testing::NotNull;
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------- test set/get --------------------------------
|
// ----------------------------- test set/get --------------------------------
|
||||||
@@ -53,5 +62,59 @@ TEST_F(MujocoTest, ReadWriteData) {
|
|||||||
mjm_deleteModel(model);
|
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<std::string> 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<char, 1000> 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
|
||||||
} // namespace mujoco
|
} // namespace mujoco
|
||||||
|
|||||||
Reference in New Issue
Block a user