During mj_parse, if mju_openResource fails pass a resource with only the filename to decoders.
This enables decoders to leverage their own file resolution systems. For example: USD can compose a stage (scene) from memory, via a file on disk, or via custom asset resolver. Passing it through to the decoder reduces the need for custom resource providers that wrap these other resolution systems. This will also enable a fun, but likely niche usecase where the decoder does procedural generation and does not have to be backed by a resource. See the tests for an example of this. PiperOrigin-RevId: 828939899 Change-Id: Ic319a685332d91198a4ae926867c834bf7f7f724
This commit is contained in:
committed by
Copybara-Service
parent
89fec8f7b2
commit
8051f40817
+30
-1
@@ -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);
|
||||
|
||||
@@ -78,49 +78,44 @@ TEST_F(DecoderPluginTest, CanDecode) {
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user