diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 8393e101..8a327def 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -74,8 +74,37 @@ mjSpec* mj_parse(const char* filename, const char* content_type, } mjResource* resource = mju_openResource("", filename, vfs, error, error_sz); + // If we are unable to open the resource, we will create our own resource with + // just the filename. This allows decoders that rely on other systems to fetch + // their content to function without a custom resource provider. + // For example, USD may use identifiers to assets that are strictly in memory + // or that are fetched on a need-be basis via URI. if (!resource) { - mju_error("Could not load resource %s", filename); + resource = (mjResource*) mju_malloc(sizeof(mjResource)); + if (resource == nullptr) { + if (error) { + strncpy(error, "could not allocate memory", error_sz); + error[error_sz - 1] = '\0'; + } + return nullptr; + } + + // clear out resource + memset(resource, 0, sizeof(mjResource)); + + // make space for filename + std::string fullname = filename; + std::size_t n = fullname.size(); + resource->name = (char*) mju_malloc(sizeof(char) * (n + 1)); + if (resource->name == nullptr) { + if (error) { + strncpy(error, "could not allocate memory", error_sz); + error[error_sz - 1] = '\0'; + } + mju_closeResource(resource); + return nullptr; + } + memcpy(resource->name, fullname.c_str(), sizeof(char) * (n + 1)); } mjSpec* spec = mju_decodeResource(resource, content_type); diff --git a/test/plugin/decoder/decoder_test.cc b/test/plugin/decoder/decoder_test.cc index f7076dd8..581b0db2 100644 --- a/test/plugin/decoder/decoder_test.cc +++ b/test/plugin/decoder/decoder_test.cc @@ -78,49 +78,44 @@ TEST_F(DecoderPluginTest, CanDecode) { )"; - char error[1024]; - - // create VFS with the XML model and a dummy mesh - mjVFS vfs; - mj_defaultVFS(&vfs); - mj_addBufferVFS(&vfs, "model.xml", xml, strlen(xml)); - mj_addBufferVFS(&vfs, "dummy.fakeformat", "0 1 2", strlen("0 1 2")); - mj_addBufferVFS(&vfs, "dummy.alsoFakeFormat", "0 1 2", strlen("0 1 2")); // Check referencing a resource via XML invokes the decoder. - mjModel* model = mj_loadXML("model.xml", &vfs, error, sizeof(error)); + char error[1024]; + mjSpec* spec = + mj_parseXMLString(xml, nullptr, error, sizeof(error)); + mjModel* model = mj_compile(spec, nullptr); ASSERT_THAT(model, testing::NotNull()) << error; EXPECT_EQ(model->nbody, 2); // world + included body EXPECT_EQ(model->ngeom, 1); mj_deleteModel(model); + mj_deleteSpec(spec); // Check mj_parse with extension .fakeformat - mjSpec* spec = - mj_parse("dummy.fakeformat", nullptr, &vfs, error, sizeof(error)); - model = mj_compile(spec, &vfs); + spec = + mj_parse("dummy.fakeformat", nullptr, nullptr, error, sizeof(error)); + model = mj_compile(spec, nullptr); EXPECT_EQ(model->nbody, 2); // world + included body EXPECT_EQ(model->ngeom, 1); mj_deleteModel(model); mj_deleteSpec(spec); // Check mj_parse with extension .alsoFakeFormat - spec = mj_parse("dummy.alsoFakeFormat", nullptr, &vfs, error, sizeof(error)); - model = mj_compile(spec, &vfs); + spec = + mj_parse("dummy.alsoFakeFormat", nullptr, nullptr, error, sizeof(error)); + model = mj_compile(spec, nullptr); EXPECT_EQ(model->nbody, 2); // world + included body EXPECT_EQ(model->ngeom, 1); mj_deleteModel(model); mj_deleteSpec(spec); // Check mj_parse with content_type - spec = mj_parse("dummy.fakeformat", "model/fakeformat", &vfs, error, + spec = mj_parse("dummy.fakeformat", "model/fakeformat", nullptr, error, sizeof(error)); - model = mj_compile(spec, &vfs); + model = mj_compile(spec, nullptr); EXPECT_EQ(model->nbody, 2); // world + included body EXPECT_EQ(model->ngeom, 1); mj_deleteModel(model); mj_deleteSpec(spec); - - mj_deleteVFS(&vfs); } } // namespace