Add various improvements for resource providers.

PiperOrigin-RevId: 529706437
Change-Id: I20385e031446674584349c301982dbe13b812477
This commit is contained in:
Kyle Bayes
2023-05-05 07:02:51 -07:00
committed by Copybara-Service
parent b798f89212
commit 67f0f5154f
23 changed files with 576 additions and 193 deletions
+2 -2
View File
@@ -621,7 +621,7 @@ void mj_saveModel(const mjModel* m, const char* filename, void* buffer, int buff
// load model from binary MJB resource
static mjModel* _mj_loadModel(const char* filename, int default_provider) {
static mjModel* _mj_loadModel(const char* filename, int vfs_provider) {
int header[4] = {0};
int expected_header[4] = {ID, sizeof(mjtNum), getnint(), getnptr()};
int info[2000];
@@ -629,7 +629,7 @@ static mjModel* _mj_loadModel(const char* filename, int default_provider) {
mjModel *m = 0;
mjResource* r = NULL;
if((r = mju_openResource(filename, default_provider)) == NULL) {
if((r = mju_openResource(filename, vfs_provider)) == NULL) {
return NULL;
}
+66 -20
View File
@@ -21,6 +21,7 @@
#include "engine/engine_plugin.h"
#include <atomic>
#include <cctype>
#include <cstddef>
#include <cstdlib>
#include <cstring>
@@ -266,14 +267,53 @@ bool PluginsAreIdentical(const mjpPlugin& plugin1, const mjpPlugin& plugin2) {
return !std::memcmp(ptr1, ptr2, remaining_size);
}
// does case insensitive comparison
bool PrefixesAreIdentical(const char* p1, const char* p2) {
int i = 0;
for (; p1[i] != '\0'; i++) {
if (std::tolower(p1[i]) != std::tolower(p2[i])) {
return false;
}
}
return p2[i] == '\0';
}
// check if two resource providers are identical
bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResourceProvider* p2) {
return (!std::strcmp(p1->prefix, p2->prefix) &&
return (PrefixesAreIdentical(p1->prefix, p2->prefix) &&
p1->open == p2->open &&
p1->read == p2->read &&
p1->close == p2->close &&
p1->data == p2->data);
}
// check if prefix is a valid URI scheme format
bool IsValidURISchemeFormat(const char* prefix) {
int len;
// prefix is NULL or empty
if (prefix == nullptr || !(len = std::strlen(prefix))) {
return false;
}
// first character must be a letter
if (!std::isalpha(prefix[0])) {
return false;
}
for (int i = 1; i < len; i++) {
// each following character must be a letter, digit, '+', '.', or '-'
if (!std::isalnum(prefix[i]) &&
(prefix[i] != '+') &&
(prefix[i] != '.') &&
(prefix[i] != '-')) {
return false;
}
}
return true;
}
} // namespace
// globally register a plugin (thread-safe), return new slot id
@@ -508,12 +548,7 @@ void mjp_defaultResourceProvider(mjpResourceProvider* provider) {
// globally register a resource provider (thread-safe), return new slot id
int mjp_registerResourceProvider(const mjpResourceProvider* provider) {
// check against reserved prefixes
int n = std::strlen(provider->prefix),
m = std::strlen(kVfsPrefix);
// one of the prefixes is a subprefix of the other
if (!std::strncmp(kVfsPrefix, provider->prefix, n) ||
!std::strncmp(kVfsPrefix, provider->prefix, m)) {
if (PrefixesAreIdentical(kVfsPrefix, provider->prefix)) {
mju_warning("provider->prefix is '%s' which is reserved", provider->prefix);
return -1;
}
@@ -523,8 +558,10 @@ int mjp_registerResourceProvider(const mjpResourceProvider* provider) {
// internal version of mjp_registerResourceProvider without prechecks on reserved prefixes
int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
if (!provider->prefix || provider->prefix[0] == '\0') {
mju_warning("provider->prefix is an empty string");
// check if prefix is valid URI scheme format
if (!IsValidURISchemeFormat(provider->prefix)) {
mju_warning("provider->prefix is '%s' which is not a valid URI scheme format",
provider->prefix);
return -1;
}
@@ -544,7 +581,7 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
std::unique_ptr<char[]> prefix;
// check if this is a VFS provider
if (!std::strcmp(mjVFS_PREFIX, provider->prefix)) {
if (PrefixesAreIdentical(mjVFS_PREFIX, provider->prefix)) {
vfs_provider = true;
}
@@ -589,13 +626,8 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
}
if (!vfs_provider && existing.prefix != nullptr) {
int n = std::strlen(provider->prefix);
int m = std::strlen(existing.prefix);
// one of the prefixes is a subprefix of the other
if (!std::strncmp(existing.prefix, provider->prefix, n) ||
!std::strncmp(existing.prefix, provider->prefix, m)) {
// if identical then return slot number
// if identical then return slot number
if (PrefixesAreIdentical(provider->prefix, existing.prefix)) {
if (ResourceProvidersAreIdentical(provider, &existing)) {
return i;
} else {
@@ -691,20 +723,34 @@ int mjp_resourceProviderCount() {
return GetGlobal<mjpResourceProvider>().count().load(std::memory_order_acquire);
}
// look up a resource provider that matches its prefix against the given resource name
// look up a resource provider that matches its prefix against the given resource scheme
const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
const int count = mjp_resourceProviderCount();
if (!resource_name || !resource_name[0]) {
return nullptr;
}
const char* ch = std::strchr(resource_name, ':');
if (ch == nullptr) {
return nullptr;
}
int n = ch - resource_name;
std::string file_prefix = std::string(resource_name, n);
// return NULL if file_prefix doesn't have a valid URI scheme syntax
if (!IsValidURISchemeFormat(file_prefix.c_str())) {
return nullptr;
}
// since multiple VFS resource providers can be registered with the same
// prefix, it doesn't make sense to try to match against them
if (!std::strncmp(kVfsPrefix, resource_name, std::strlen(kVfsPrefix))) {
if (PrefixesAreIdentical(kVfsPrefix, file_prefix.c_str())) {
return nullptr;
}
Global<mjpResourceProvider>& global = GetGlobal<mjpResourceProvider>();
auto lock = global.lock_mutex_exclusively();
PluginTable<mjpResourceProvider>* table = &global.table();
int found_slot = 0;
@@ -717,7 +763,7 @@ const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
const char *prefix = provider.prefix;
if (prefix != nullptr &&
!std::strncmp(prefix, resource_name, std::strlen(prefix))) {
PrefixesAreIdentical(prefix, file_prefix.c_str())) {
return &provider;
}
}
+55 -10
View File
@@ -30,6 +30,21 @@ typedef struct {
int nbuffer;
} file_buffer;
// helper function to fill data from resource provider into provider
static void fillResource(const mjpResourceProvider* provider, mjResource* resource) {
if (provider == NULL) {
resource->read = NULL;
resource->close = NULL;
resource->getdir = NULL;
resource->provider_data = NULL;
} else {
resource->read = provider->read;
resource->close = provider->close;
resource->getdir = provider->getdir;
resource->provider_data = provider->data;
}
}
// open the given resource; if the name doesn't have a prefix matching with a
// resource provider, then the default_provider is used
@@ -54,9 +69,7 @@ mjResource* mju_openResource(const char* name, int default_provider) {
// find provider based off prefix of name
provider = mjp_getResourceProvider(name);
if (provider != NULL) {
resource->read = provider->read;
resource->close = provider->close;
resource->provider_data = provider->data;
fillResource(provider, resource);
if (provider->open(resource)) {
return resource;
}
@@ -79,9 +92,8 @@ mjResource* mju_openResource(const char* name, int default_provider) {
mju_free(resource);
return NULL;
}
resource->read = provider->read;
resource->close = provider->close;
resource->provider_data = provider->data;
fillResource(provider, resource);
if (provider->open(resource)) {
return resource;
}
@@ -96,9 +108,7 @@ mjResource* mju_openResource(const char* name, int default_provider) {
// lastly fallback to OS filesystem
else {
resource->read = NULL;
resource->close = NULL;
resource->provider_data = NULL;
fillResource(NULL, resource);
resource->data = mju_malloc(sizeof(file_buffer));
file_buffer* fb = (file_buffer*) resource->data;
fb->buffer = mju_fileToMemory(name, &(fb->nbuffer));
@@ -152,7 +162,7 @@ int mju_readResource(mjResource* resource, const void** buffer) {
}
// if provider is NULL, then OS filesystem is used
// if provider read callback is NULL, then OS filesystem is used
const file_buffer* fb = (file_buffer*) resource->data;
*buffer = fb->buffer;
return fb->nbuffer;
@@ -160,6 +170,41 @@ int mju_readResource(mjResource* resource, const void** buffer) {
// get directory path of resource
void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) {
*dir = NULL;
*ndir = 0;
if (resource == NULL) {
return;
}
// provider is not OS filesystem
if (resource->read) {
if (resource->getdir) {
resource->getdir(resource, dir, ndir);
}
} else {
mju_getfiledir(resource->name, dir, ndir);
}
}
// get directory path of file in OS filesystem
void mju_getfiledir(const char* filename, const char** dir, int* ndir) {
*dir = NULL;
*ndir = 0;
char* ch;
if ((filename != NULL) &&
((ch = strrchr(filename, '/')) ||
(ch = strrchr(filename, '\\')))) {
*dir = filename;
*ndir = (int)(ch - filename) + 1;
}
}
// read file into memory buffer (allocated here with mju_malloc)
void* mju_fileToMemory(const char* filename, int* filesize) {
// open file
+6
View File
@@ -34,6 +34,12 @@ MJAPI void mju_closeResource(mjResource* resource);
// return negative value if error
MJAPI int mju_readResource(mjResource* resource, const void** buffer);
// sets for a resource with a name partitioned as {dir}{filename}, the dir and ndir pointers
MJAPI void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir);
// get directory of a file in the OS filesystem
void mju_getfiledir(const char* filename, const char** dir, int* ndir);
// read file into memory buffer (allocated here with mju_malloc)
void* mju_fileToMemory(const char* filename, int* filesize);
+13
View File
@@ -251,6 +251,18 @@ static void vfs_close_callback(mjResource* resource) {
// getdir callback for the VFS resource provider
static void vfs_getdir_callback(mjResource* resource, const char** dir, int* ndir) {
*dir = NULL;
*ndir = 0;
if (resource) {
mju_getfiledir(resource->name, dir, ndir);
}
}
// registers a VFS resource provider; returns the index of the provider
int mj_registerVfsProvider(const mjVFS* vfs) {
mjpResourceProvider provider = {
@@ -258,6 +270,7 @@ int mj_registerVfsProvider(const mjVFS* vfs) {
.open = &vfs_open_callback,
.read = &vfs_read_callback,
.close = &vfs_close_callback,
.getdir = &vfs_getdir_callback,
.data = (void*) vfs
};
+4 -4
View File
@@ -176,7 +176,7 @@ template <typename T> static T* VecToArray(std::vector<T>& vector, bool clear =
// compiler
void mjCMesh::Compile(int default_provider) {
void mjCMesh::Compile(int vfs_provider) {
// load file
if (!file.empty()) {
// remove path from file if necessary
@@ -193,7 +193,7 @@ void mjCMesh::Compile(int default_provider) {
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, default_provider);
mjResource* resource = LoadResource(filename, vfs_provider);
try {
if (!strcasecmp(ext.c_str(), ".stl")) {
@@ -1535,7 +1535,7 @@ mjCSkin::~mjCSkin() {
// compiler
void mjCSkin::Compile(int default_provider) {
void mjCSkin::Compile(int vfs_provider) {
// load file
if (!file.empty()) {
@@ -1564,7 +1564,7 @@ void mjCSkin::Compile(int default_provider) {
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, default_provider);
mjResource* resource = LoadResource(filename, vfs_provider);
try {
LoadSKN(resource);
+7 -7
View File
@@ -2393,7 +2393,7 @@ static void warninghandler(const char* msg) {
// compiler
mjModel* mjCModel::Compile(int default_provider) {
mjModel* mjCModel::Compile(int vfs_provider) {
// The volatile keyword is necessary to prevent a possible memory leak due to
// an interaction between longjmp and compiler optimization. Specifically, at
// the point where the setjmp takes places, these pointers have never been
@@ -2424,7 +2424,7 @@ mjModel* mjCModel::Compile(int default_provider) {
// TryCompile resulted in an mju_error which was converted to a longjmp.
throw mjCError(0, "engine error: %s", errortext);
}
TryCompile(*const_cast<mjModel**>(&m), *const_cast<mjData**>(&data), default_provider);
TryCompile(*const_cast<mjModel**>(&m), *const_cast<mjData**>(&data), vfs_provider);
} catch (mjCError err) {
// deallocate everything allocated in Compile
mj_deleteModel(m);
@@ -2450,7 +2450,7 @@ mjModel* mjCModel::Compile(int default_provider) {
}
void mjCModel::TryCompile(mjModel*& m, mjData*& d, int default_provider) {
void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) {
// check if nan test works
double test = mjNAN;
if (mjuu_defined(test)) {
@@ -2527,7 +2527,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int default_provider) {
// compile meshes (needed for geom compilation)
for (int i=0; i<meshes.size(); i++) {
meshes[i]->Compile(default_provider);
meshes[i]->Compile(vfs_provider);
}
// automatically set nuser fields
@@ -2586,9 +2586,9 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int default_provider) {
}
// compile all other objects except for keyframes
for (int i=0; i<skins.size(); i++) skins[i]->Compile(default_provider);
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(default_provider);
for (int i=0; i<textures.size(); i++) textures[i]->Compile(default_provider);
for (int i=0; i<skins.size(); i++) skins[i]->Compile(vfs_provider);
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(vfs_provider);
for (int i=0; i<textures.size(); i++) textures[i]->Compile(vfs_provider);
for (int i=0; i<materials.size(); i++) materials[i]->Compile();
for (int i=0; i<pairs.size(); i++) pairs[i]->Compile();
for (int i=0; i<excludes.size(); i++) excludes[i]->Compile();
+3 -3
View File
@@ -65,7 +65,7 @@ class mjCModel {
mjCModel(); // constructor
~mjCModel(); // destructor
mjModel* Compile(int default_provider = 0); // COMPILER: construct mjModel
mjModel* Compile(int vfs_provider = 0); // COMPILER: construct mjModel
bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back
void FuseStatic(void); // fuse static bodies with parent
void FuseReindex(mjCBody* body); // reindex elements during fuse
@@ -163,8 +163,8 @@ class mjCModel {
int nuser_sensor; // number of mjtNums in sensor_user
private:
void TryCompile(mjModel*& m, mjData*& d, int default_provider);
mjModel* _Compile(int default_provider);
void TryCompile(mjModel*& m, mjData*& d, int vfs_provider);
mjModel* _Compile(int vfs_provider);
void Clear(void); // clear objects allocated by Compile
+14 -14
View File
@@ -2114,7 +2114,7 @@ void mjCHField::LoadPNG(mjResource* resource) {
// compiler
void mjCHField::Compile(int default_provider) {
void mjCHField::Compile(int vfs_provider) {
// check size parameters
for (int i=0; i<4; i++)
if (size[i]<=0)
@@ -2136,7 +2136,7 @@ void mjCHField::Compile(int default_provider) {
// make filename
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, default_provider);
mjResource* resource = LoadResource(filename, vfs_provider);
// load depending on format
string ext = mjuu_getext(filename);
@@ -2530,12 +2530,12 @@ void mjCTexture::LoadCustom(mjResource* resource,
// load from PNG or custom file, flip if specified
void mjCTexture::LoadFlip(string filename, int default_provider,
void mjCTexture::LoadFlip(string filename, int vfs_provider,
std::vector<unsigned char>& image,
unsigned int& w, unsigned int& h) {
// dispatch to PNG or Custom loaded
string ext = mjuu_getext(filename);
mjResource* resource = LoadResource(filename, default_provider);
mjResource* resource = LoadResource(filename, vfs_provider);
try {
if (!strcasecmp(ext.c_str(), ".png")) {
@@ -2597,11 +2597,11 @@ void mjCTexture::LoadFlip(string filename, int default_provider,
// load 2D
void mjCTexture::Load2D(string filename, int default_provider) {
void mjCTexture::Load2D(string filename, int vfs_provider) {
// load PNG or custom
unsigned int w, h;
std::vector<unsigned char> image;
LoadFlip(filename, default_provider, image, w, h);
LoadFlip(filename, vfs_provider, image, w, h);
// assign size
width = w;
@@ -2620,7 +2620,7 @@ void mjCTexture::Load2D(string filename, int default_provider) {
// load cube or skybox from single file (repeated or grid)
void mjCTexture::LoadCubeSingle(string filename, int default_provider) {
void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) {
// check gridsize
if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) {
throw mjCError(this,
@@ -2631,7 +2631,7 @@ void mjCTexture::LoadCubeSingle(string filename, int default_provider) {
// load PNG or custom
unsigned int w, h;
std::vector<unsigned char> image;
LoadFlip(filename, default_provider, image, w, h);
LoadFlip(filename, vfs_provider, image, w, h);
// check gridsize for compatibility
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
@@ -2720,7 +2720,7 @@ void mjCTexture::LoadCubeSingle(string filename, int default_provider) {
// load cube or skybox from separate file
void mjCTexture::LoadCubeSeparate(int default_provider) {
void mjCTexture::LoadCubeSeparate(int vfs_provider) {
// keep track of which faces were defined
int loaded[6] = {0, 0, 0, 0, 0, 0};
@@ -2738,7 +2738,7 @@ void mjCTexture::LoadCubeSeparate(int default_provider) {
// load PNG or custom
unsigned int w, h;
std::vector<unsigned char> image;
LoadFlip(filename, default_provider, image, w, h);
LoadFlip(filename, vfs_provider, image, w, h);
// PNG must be square
if (w!=h) {
@@ -2792,7 +2792,7 @@ void mjCTexture::LoadCubeSeparate(int default_provider) {
// compiler
void mjCTexture::Compile(int default_provider) {
void mjCTexture::Compile(int vfs_provider) {
// builtin
if (builtin!=mjBUILTIN_NONE) {
// check size
@@ -2835,9 +2835,9 @@ void mjCTexture::Compile(int default_provider) {
// dispatch
if (type==mjTEXTURE_2D) {
Load2D(filename, default_provider);
Load2D(filename, vfs_provider);
} else {
LoadCubeSingle(filename, default_provider);
LoadCubeSingle(filename, vfs_provider);
}
}
@@ -2865,7 +2865,7 @@ void mjCTexture::Compile(int default_provider) {
}
// only cube and skybox
LoadCubeSeparate(default_provider);
LoadCubeSeparate(vfs_provider);
}
// make sure someone allocated data; SHOULD NOT OCCUR
+8 -8
View File
@@ -543,7 +543,7 @@ class mjCMesh: public mjCBase {
private:
mjCMesh(mjCModel* = 0, mjCDef* = 0); // constructor
~mjCMesh(); // destructor
void Compile(int default_provider); // compiler
void Compile(int vfs_provider); // compiler
void LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format
void LoadSTL(mjResource* resource); // load mesh in STL BIN format
void LoadMSH(mjResource* resource); // load mesh in MSH BIN format
@@ -625,7 +625,7 @@ class mjCSkin: public mjCBase {
private:
mjCSkin(mjCModel* = 0); // constructor
~mjCSkin(); // destructor
void Compile(int default_provider); // compiler
void Compile(int vfs_provider); // compiler
void LoadSKN(mjResource* resource); // load skin in SKN BIN format
int matid; // material id
@@ -651,7 +651,7 @@ class mjCHField : public mjCBase {
private:
mjCHField(mjCModel* model); // constructor
~mjCHField(); // destructor
void Compile(int default_provider); // compiler
void Compile(int vfs_provider); // compiler
void LoadCustom(mjResource* resource); // load from custom format
void LoadPNG(mjResource* resource); // load from PNG format
@@ -695,15 +695,15 @@ class mjCTexture : public mjCBase {
private:
mjCTexture(mjCModel*); // constructor
~mjCTexture(); // destructior
void Compile(int default_provider); // compiler
void Compile(int vfs_provider); // compiler
void Builtin2D(void); // make builtin 2D
void BuiltinCube(void); // make builtin cube
void Load2D(std::string filename, int default_provider); // load 2D from file
void LoadCubeSingle(std::string filename, int default_provider); // load cube from single file
void LoadCubeSeparate(int default_provider); // load cube from separate files
void Load2D(std::string filename, int vfs_provider); // load 2D from file
void LoadCubeSingle(std::string filename, int vfs_provider); // load cube from single file
void LoadCubeSeparate(int vfs_provider); // load cube from separate files
void LoadFlip(std::string filename, int default_provider, // load and flip
void LoadFlip(std::string filename, int vfs_provider, // load and flip
std::vector<unsigned char>& image,
unsigned int& w, unsigned int& h);
+7 -19
View File
@@ -22,6 +22,7 @@
#include <string_view>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "engine/engine_macro.h"
#include "engine/engine_util_spatial.h"
@@ -558,6 +559,12 @@ bool mjuu_isabspath(string path) {
return false;
}
// path is scheme:filename which we consider an absolute path
// e.g. file URI's are always absolute paths
if (mjp_getResourceProvider(path.c_str()) != nullptr) {
return true;
}
// check first char
const char* str = path.c_str();
if (str[0]=='\\' || str[0]=='/') {
@@ -573,25 +580,6 @@ bool mjuu_isabspath(string path) {
}
// get directory path of file
string mjuu_getfiledir(string filename) {
// no filename
if (filename.empty()) {
return "";
}
// find last pathsymbol
size_t last = filename.find_last_of("/\\");
// no pathsymbol: unknown dir
if (last==string::npos) {
return "";
}
// extract path from filename
return filename.substr(0, last+1);
}
// assemble full filename
string mjuu_makefullname(string filedir, string meshdir, string filename) {
-3
View File
@@ -149,9 +149,6 @@ std::string mjuu_getext(std::string_view filename);
// check if path is absolute
bool mjuu_isabspath(std::string path);
// get path from filename
std::string mjuu_getfiledir(std::string filename);
// assemble full filename
std::string mjuu_makefullname(std::string filedir, std::string meshdir, std::string filename);
+22 -14
View File
@@ -129,7 +129,7 @@ bool mjWriteXML(mjCModel* model, string filename, char* error, int error_sz) {
// find include elements recursively, replace them with subtree from xml file
static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
int default_provider, vector<string>& included) {
int vfs_provider, vector<string>& included) {
// include element: process
if (!strcasecmp(elem->Value(), "include")) {
// make sure include has no children
@@ -152,9 +152,9 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
// get data source
mjResource *resource = nullptr;
const char* xmlstring = nullptr;
if ((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
if ((resource = mju_openResource(filename.c_str(), vfs_provider)) == nullptr) {
// load from OS filesystem
if (!default_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
if (!vfs_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
throw mjXError(elem, "Could not open file '%s'", filename.c_str());
}
}
@@ -215,14 +215,14 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
}
// run XMLInclude on first new child
return mjIncludeXML(first->ToElement(), dir, default_provider, included);
return mjIncludeXML(first->ToElement(), dir, vfs_provider, included);
}
// otherwise check all child elements, return self
else {
XMLElement* child = elem->FirstChildElement();
while (child) {
child = mjIncludeXML(child, dir, default_provider, included);
child = mjIncludeXML(child, dir, vfs_provider, included);
if (child) {
child = child->NextSiblingElement();
}
@@ -234,7 +234,7 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
// Main parser function
mjCModel* mjParseXML(const char* filename, int default_provider, char* error, int error_sz) {
mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int error_sz) {
LocaleOverride locale_override;
// check arguments
@@ -254,9 +254,9 @@ mjCModel* mjParseXML(const char* filename, int default_provider, char* error, in
// get data source
mjResource* resource = nullptr;
const char* xmlstring = nullptr;
if ((resource = mju_openResource(filename, default_provider)) == nullptr) {
if ((resource = mju_openResource(filename, vfs_provider)) == nullptr) {
// load from OS filesystem
if (!default_provider || (resource = mju_openResource(filename, 0)) == nullptr) {
if (!vfs_provider || (resource = mju_openResource(filename, 0)) == nullptr) {
if (error) {
snprintf(error, error_sz, "mjParseXML: could not open file '%s'", filename);
}
@@ -284,29 +284,37 @@ mjCModel* mjParseXML(const char* filename, int default_provider, char* error, in
XMLDocument doc;
doc.Parse(xmlstring, buffer_size);
// close resource
mju_closeResource(resource);
// error checking
if (doc.Error()) {
if (error) {
snprintf(error, error_sz, "XML parse error %d:\n%s\n",
doc.ErrorID(), doc.ErrorStr());
}
mju_closeResource(resource);
return nullptr;
}
// get top-level element
XMLElement* root = doc.RootElement();
if (!root) {
mju_closeResource(resource);
mjCopyError(error, "XML root element not found", error_sz);
return nullptr;
}
// create model, set filedir
model = new mjCModel;
model->modelfiledir = mjuu_getfiledir(filename);
const char* dir;
int ndir = 0;
mju_getResourceDir(resource, &dir, &ndir);
if (dir != nullptr) {
model->modelfiledir = std::string(dir, ndir);
} else {
model->modelfiledir = "";
}
// close resource
mju_closeResource(resource);
// parse with exceptions
try {
@@ -314,7 +322,7 @@ mjCModel* mjParseXML(const char* filename, int default_provider, char* error, in
// find include elements, replace them with subtree from xml file
vector<string> included;
included.push_back(filename);
mjIncludeXML(root, model->modelfiledir, default_provider, included);
mjIncludeXML(root, model->modelfiledir, vfs_provider, included);
// parse MuJoCo model
mjXReader parser;
+1 -1
View File
@@ -26,7 +26,7 @@
bool mjWriteXML(mjCModel* model, std::string filename, char* error, int error_sz);
// Main parser function
mjCModel* mjParseXML(const char* filename, int default_provider, char* error, int error_sz);
mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int error_sz);
#endif // MUJOCO_SRC_XML_XML_H_
+3 -3
View File
@@ -83,19 +83,19 @@ void mj_deactivate(void) {
// mj_loadXML helper function
mjModel* _loadXML(const char* filename, int default_provider,
mjModel* _loadXML(const char* filename, int vfs_provider,
char* error, int error_sz) {
// serialize access to themodel
std::lock_guard<std::mutex> lock(themutex);
// parse new model
mjCModel* newmodel = mjParseXML(filename, default_provider, error, error_sz);
mjCModel* newmodel = mjParseXML(filename, vfs_provider, error, error_sz);
if (!newmodel) {
return nullptr;
}
// compile new model
mjModel* m = newmodel->Compile(default_provider);
mjModel* m = newmodel->Compile(vfs_provider);
if (!m) {
mjCopyError(error, newmodel->GetError().message, error_sz);
delete newmodel;