diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 5e8214b4..4dbf3b0e 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -67,20 +67,36 @@ mjSpec* mj_copySpec(const mjSpec* s) { // parse file into spec mjSpec* mj_parse(const char* filename, const char* content_type, const mjVFS* vfs, char* error, int error_sz) { + mjVFS local_vfs; + mujoco::user::Cleanup cleanup; + // early exit for existing XML workflow auto filepath = mujoco::user::FilePath(filename); if (filepath.Ext() == ".xml" || (content_type && std::strcmp(content_type, "text/xml") == 0)) { return mj_parseXML(filename, vfs, error, error_sz); } + // If no VFS is provided, we'll create our own temporary one for the duration + // of this function. + if (vfs == nullptr) { + mj_defaultVFS(&local_vfs); + cleanup += [&local_vfs](){ mj_deleteVFS(&local_vfs); }; + + vfs = &local_vfs; + } + 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) { + if (resource) { + cleanup += [resource](){ mju_closeResource(resource); }; + } else { resource = (mjResource*) mju_malloc(sizeof(mjResource)); + cleanup += [resource](){ if (resource) mju_free(resource); }; + if (resource == nullptr) { if (error) { strncpy(error, "could not allocate memory", error_sz); @@ -96,20 +112,19 @@ mjSpec* mj_parse(const char* filename, const char* content_type, std::string fullname = filename; std::size_t n = fullname.size(); resource->name = (char*) mju_malloc(sizeof(char) * (n + 1)); + cleanup += [resource](){ if (resource) mju_free(resource->name); }; + 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, vfs); - mju_closeResource(resource); - return spec; + return mju_decodeResource(resource, content_type, vfs); } // compile model diff --git a/src/user/user_util.h b/src/user/user_util.h index 31abf544..2eeb3a0f 100644 --- a/src/user/user_util.h +++ b/src/user/user_util.h @@ -18,9 +18,12 @@ #include #include #include +#include +#include #include #include #include +#include #include const double mjEPS = 1E-14; // minimum value in various calculations @@ -235,6 +238,15 @@ class FilePath { std::string path_; }; +// utility class for scoping resources to functions +struct Cleanup { + using Fn = std::function; + ~Cleanup() { for (auto& f : cleanup) f(); } + void operator+=(Fn f) { cleanup.push_front(std::move(f)); } + std::deque cleanup; +}; + + // read file into memory buffer std::vector FileToMemory(const char* filename);