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
+70
View File
@@ -671,6 +671,17 @@ information is then filled-in by the simulator.
.. mujoco-include:: mjContact .. mujoco-include:: mjContact
.. _mjResource:
mjResource
~~~~~~~~~~
A resource is an abstraction of a file in a filesystem. The name field is the unique name of the resource while the
other fields are populated by a :ref:`resource provider <exProvider>`.
.. mujoco-include:: mjResource
.. _mjVFS: .. _mjVFS:
mjVFS mjVFS
@@ -960,7 +971,15 @@ triggered by the compiler and the engine during various phases of the computatio
.. mujoco-include:: mjpPlugin .. mujoco-include:: mjpPlugin
.. _mjpResourceProvider:
mjpResourceProvider
~~~~~~~~~~~~~~~~~~~
This data structure contains the definition of a :ref:`resource provider <exProvider>`. It contains a set of callbacks
used for opening and reading resources.
.. mujoco-include:: mjpResourceProvider
.. _tyFunction: .. _tyFunction:
@@ -1074,6 +1093,57 @@ mjfItemEnable
This is the function type of the predicate function used by the UI framework to determine if each item is enabled or This is the function type of the predicate function used by the UI framework to determine if each item is enabled or
disabled. disabled.
.. _tyRPCallbacks:
Resource Provider Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
These callbacks are used by :ref:`resource providers<exProvider>`.
.. _mjfOpenResource:
mjfOpenResource
~~~~~~~~~~~~~~~
.. code-block:: C
typedef int (*mjfOpenResource)(mjResource* resource);
This callback is for opeing a resource; returns zero on failure.
.. _mjfReadResource:
mjfReadResource
~~~~~~~~~~~~~~~
.. code-block:: C
typedef int (*mjfReadResource)(mjResource* resource, const void** buffer);
This callback is for reading a resource. Returns number of bytes stored in buffer and returns -1 on error.
.. _mjfCloseResource:
mjfCloseResource
~~~~~~~~~~~~~~~~
.. code-block:: C
typedef void (*mjfCloseResource)(mjResource* resource);
This callback is for closing a resource, and is responsible for freeing any allocated memory.
.. _mjfGetResourceDir:
mjfGetResourceDir
~~~~~~~~~~~~~~~~~
.. code-block:: C
typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* ndir);
This callback is for returning the directory of a resource, by setting dir to the directory string with ndir being size
of directory string.
.. _tyNotes: .. _tyNotes:
+2 -1
View File
@@ -26,6 +26,7 @@ General
- Added :ref:`mj_multiRay` function for intersecting multiple rays emanating from a single point. - Added :ref:`mj_multiRay` function for intersecting multiple rays emanating from a single point.
This is significantly faster than calling :ref:`mj_ray` multiple times. This is significantly faster than calling :ref:`mj_ray` multiple times.
- Increased ``mjMAXUIITEM`` (maximum number of UI elements per section in Simulate) to 100. - Increased ``mjMAXUIITEM`` (maximum number of UI elements per section in Simulate) to 100.
- Added :ref:`documentation<exProvider>` for resource providers.
Version 2.3.5 (April 25, 2023) Version 2.3.5 (April 25, 2023)
------------------------------ ------------------------------
@@ -66,7 +67,7 @@ General
#. Added midphase and broadphase collision statistics to :ref:`mjData`. #. Added midphase and broadphase collision statistics to :ref:`mjData`.
#. Added documentation for :ref:`engine plugins<exPlugin>`. #. Added documentation for :ref:`engine plugins<exPlugin>`.
#. Added struct information to the ``introspect`` module. #. Added struct information to the ``introspect`` module.
#. Added a new extension mechanism called "resource provider" . This extensible mechanism allows MuJoCo #. Added a new extension mechanism called :ref:`resource providers<exProvider>`. This extensible mechanism allows MuJoCo
to read assets from data sources other than the local OS filesystem or to read assets from data sources other than the local OS filesystem or
the :ref:`Virtual file system<Virtualfilesystem>`. the :ref:`Virtual file system<Virtualfilesystem>`.
+9 -5
View File
@@ -624,6 +624,9 @@ struct mjResource_ {
// closing callback from resource provider // closing callback from resource provider
void (*close)(struct mjResource_* resource); void (*close)(struct mjResource_* resource);
// getdir callback from resource provider
void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir);
}; };
typedef struct mjResource_ mjResource; typedef struct mjResource_ mjResource;
struct mjOption_ { // physics options struct mjOption_ { // physics options
@@ -1198,11 +1201,12 @@ struct mjModel_ {
}; };
typedef struct mjModel_ mjModel; typedef struct mjModel_ mjModel;
struct mjpResourceProvider_ { struct mjpResourceProvider_ {
const char* prefix; // prefix for match against a resource name const char* prefix; // prefix for match against a resource name
mjfOpenResource open; // opening callback mjfOpenResource open; // opening callback
mjfReadResource read; // reading callback mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback mjfCloseResource close; // closing callback
void* data; // opaque data pointer (resource invariant) mjfGetResourceDir getdir; // getdir callback (optional)
void* data; // opaque data pointer (resource invariant)
}; };
typedef struct mjpResourceProvider_ mjpResourceProvider; typedef struct mjpResourceProvider_ mjpResourceProvider;
typedef enum mjtPluginCapabilityBit_ { typedef enum mjtPluginCapabilityBit_ {
+129 -2
View File
@@ -3,8 +3,8 @@
Extensions Extensions
---------- ----------
This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided only This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided by
via **engine plugins**. via :ref:`engine plugins<exPlugin>` and :ref:`resource providers<exProvider>`.
.. _exPlugin: .. _exPlugin:
@@ -246,3 +246,130 @@ A future version of this section will include:
* How to declare custom MJCF attributes for a plugin. * How to declare custom MJCF attributes for a plugin.
* Things that developers need to keep in mind in order to ensure that plugins function correctly when :ref:`mjData` is * Things that developers need to keep in mind in order to ensure that plugins function correctly when :ref:`mjData` is
copied, stepped, or reset. copied, stepped, or reset.
.. _exProvider:
Resource providers
~~~~~~~~~~~~~~~~~~
Resource providers extend MuJoCo to load assets (XML files, meshes, textures, and etc.) that don't necessarily come from
the OS filesystem or the Virtual File System (:ref:`mjVFS`). For example, downloading assets from the Internet could be
implemented as a resource provider. These extensions are handle abstractly in MuJoCo via the :ref:`mjResource` struct.
.. _exProviderStructure:
Overview
^^^^^^^^
Creating a new resource provider works by registering a :ref:`mjpResourceProvider` struct via
:ref:`mjp_registerResourceProvider` in a global table. Once a resource provider is registered it can be used by all
loading functions. The :ref:`mjpResourceProvider` struct stores three types of fields:
.. _Uniform Resource Identifier: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
Resource prefix
Resources are identified by prefixes in their name. The chosen prefix should have a valid `Uniform Resource
Identifier`_ (URI) scheme syntax. Resource names should also have a valid URI syntax, however this isn't enforced. A
resource name with the syntax ``{prefix}:{filename}`` will match a provider using the scheme ``prefix``. For
instance, a resource provider accessing assets via the Internet might use ``http`` as its scheme. In this case a
resource with the name ``http://www.example.com/myasset.obj`` would match against this resource provider. Schemes are
case-insensitive so that ``HTTP://www.example.com/myasset.obj`` will also match. Note the importance of the colon.
URI syntax requires that a colon follows the prefix in a resource name in order to match against a scheme. For example
``https://www.example.com/myasset.obj`` would NOT be a match since the scheme is designated as ``https``.
Callbacks
There are three callbacks that a resource provider is required to implement: :ref:`open<mjfOpenResource>`,
:ref:`read<mjfReadResource>`, and :ref:`close<mjfCloseResource>`. A fourth callback :ref:`getdir<mjfGetResourceDir>`
which is optional. More details on these callbacks are given below.
Data Pointer
Lastly, there's an opaque data pointer for the provider to pass data into the callbacks. This data pointer is constant
within a given model.
Resource providers work via callbacks:
- :ref:`mjfOpenResource<mjfOpenResource>`: The open callback takes a single parameter of type :ref:`mjResource`. The
name field of the resource should be used to verify that the resource exists and populate the resource data field with
any extra information needed for the resource. On failure this callback should return 0 (false) or else 1 (true).
- :ref:`mjfReadResource<mjfReadResource>`: The read callback takes as arguments a :ref:`mjResource` and a pointer to a
void pointer called the ``buffer``. The read callback should point the ``buffer`` pointer to the location of where the
bytes of the resource can be read and return the number of bytes pointed to in the ``buffer``. On failure, this
callback should return -1.
- :ref:`mjfCloseResource<mjfCloseResource>`: This callback takes a single parameter of type :ref:`mjResource`, and
should be used to free any memory allocated in the data field in the supplied resource.
- :ref:`mjfGetResourceDir<mjfGetResourceDir>`: This callback is optional and is used to extract the directory from a
resource name. For example, the resource name ``http://www.example.com/myasset.obj`` would have
``http://www.example.com/`` as its directory.
.. _exProviderUsage:
Usage
^^^^^
When a resource provider is registered, it can be used immediately to open assets. If the asset filename has a prefix
that matches with the prefix of a registered provider, then that provider will be used to load the asset.
.. _exProviderExample:
Example
"""""""
.. _data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme
This section provides a basic example of a resource provider that reads from a `data URI scheme`_. First we implement
the callbacks:
.. code-block:: C
int data_open_callback(mjResource* resource) {
// call some util function to validate
if (!is_valid_data_uri(resource->name)) {
return 0; // return failure
}
// some upper bound for the data
resource->data = mju_malloc(get_data_uri_size(resource->name));
if (resource->data == NULL) {
return 0; // return failure
}
// fill data from string (some util function)
get_data_uri(resource->name, &data);
}
int str_read_callback(mjResource* resource, const void** buffer) {
*buffer = resource->data;
return get_data_uri_size(resource->name);
}
void str_close_callback(mjResource* resource) {
mju_free(resource->data);
}
Next we create the resource provider and register it with MuJoCo:
.. code-block:: C
mjpResourceProvider resourceProvider = {
.prefix = "data",
.open = str_open_callback,
.read = str_read_callback,
.close = str_close_callback,
.getdir = NULL
};
// return positive number on success
if (!mjp_registerResourceProvider(&resourceProvider)) {
// ...
// return failure
}
Now we can write assets as strings in our MJCF files:
.. code-block:: xml
<asset>
<texture name="grid" file="grid.png" type="2d"/>
<mesh file="data:model/obj;base65,I215IG9iamVjdA0KdiAxIDAgMA0KdiAwIDEgMA0KdiAwIDAgMQ=="/>
...
</asset>
+3
View File
@@ -390,6 +390,9 @@ struct mjResource_ {
// closing callback from resource provider // closing callback from resource provider
void (*close)(struct mjResource_* resource); void (*close)(struct mjResource_* resource);
// getdir callback from resource provider
void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir);
}; };
typedef struct mjResource_ mjResource; typedef struct mjResource_ mjResource;
+11 -6
View File
@@ -22,7 +22,7 @@
//---------------------------------- Resource Provider --------------------------------------------- //---------------------------------- Resource Provider ---------------------------------------------
#define mjVFS_PREFIX "vfs://" // prefix for VFS providers #define mjVFS_PREFIX "vfs" // prefix for VFS providers
// callback for opeing a resource, returns zero on failure // callback for opeing a resource, returns zero on failure
typedef int (*mjfOpenResource)(mjResource* resource); typedef int (*mjfOpenResource)(mjResource* resource);
@@ -34,13 +34,18 @@ typedef int (*mjfReadResource)(mjResource* resource, const void** buffer);
// callback for closing a resource (responsible for freeing any allocated memory) // callback for closing a resource (responsible for freeing any allocated memory)
typedef void (*mjfCloseResource)(mjResource* resource); typedef void (*mjfCloseResource)(mjResource* resource);
// callback for returning the directory of a resource
// sets dir to directory string with ndir being size of directory string
typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* ndir);
// struct describing a single resource provider // struct describing a single resource provider
struct mjpResourceProvider_ { struct mjpResourceProvider_ {
const char* prefix; // prefix for match against a resource name const char* prefix; // prefix for match against a resource name
mjfOpenResource open; // opening callback mjfOpenResource open; // opening callback
mjfReadResource read; // reading callback mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback mjfCloseResource close; // closing callback
void* data; // opaque data pointer (resource invariant) mjfGetResourceDir getdir; // getdir callback (optional)
void* data; // opaque data pointer (resource invariant)
}; };
typedef struct mjpResourceProvider_ mjpResourceProvider; typedef struct mjpResourceProvider_ mjpResourceProvider;
+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 // 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 header[4] = {0};
int expected_header[4] = {ID, sizeof(mjtNum), getnint(), getnptr()}; int expected_header[4] = {ID, sizeof(mjtNum), getnint(), getnptr()};
int info[2000]; int info[2000];
@@ -629,7 +629,7 @@ static mjModel* _mj_loadModel(const char* filename, int default_provider) {
mjModel *m = 0; mjModel *m = 0;
mjResource* r = NULL; mjResource* r = NULL;
if((r = mju_openResource(filename, default_provider)) == NULL) { if((r = mju_openResource(filename, vfs_provider)) == NULL) {
return NULL; return NULL;
} }
+66 -20
View File
@@ -21,6 +21,7 @@
#include "engine/engine_plugin.h" #include "engine/engine_plugin.h"
#include <atomic> #include <atomic>
#include <cctype>
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@@ -266,14 +267,53 @@ bool PluginsAreIdentical(const mjpPlugin& plugin1, const mjpPlugin& plugin2) {
return !std::memcmp(ptr1, ptr2, remaining_size); 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 // check if two resource providers are identical
bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResourceProvider* p2) { 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->open == p2->open &&
p1->read == p2->read && p1->read == p2->read &&
p1->close == p2->close && p1->close == p2->close &&
p1->data == p2->data); 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 } // namespace
// globally register a plugin (thread-safe), return new slot id // 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 // globally register a resource provider (thread-safe), return new slot id
int mjp_registerResourceProvider(const mjpResourceProvider* provider) { int mjp_registerResourceProvider(const mjpResourceProvider* provider) {
// check against reserved prefixes // check against reserved prefixes
int n = std::strlen(provider->prefix), if (PrefixesAreIdentical(kVfsPrefix, 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)) {
mju_warning("provider->prefix is '%s' which is reserved", provider->prefix); mju_warning("provider->prefix is '%s' which is reserved", provider->prefix);
return -1; return -1;
} }
@@ -523,8 +558,10 @@ int mjp_registerResourceProvider(const mjpResourceProvider* provider) {
// internal version of mjp_registerResourceProvider without prechecks on reserved prefixes // internal version of mjp_registerResourceProvider without prechecks on reserved prefixes
int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) { int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
if (!provider->prefix || provider->prefix[0] == '\0') { // check if prefix is valid URI scheme format
mju_warning("provider->prefix is an empty string"); if (!IsValidURISchemeFormat(provider->prefix)) {
mju_warning("provider->prefix is '%s' which is not a valid URI scheme format",
provider->prefix);
return -1; return -1;
} }
@@ -544,7 +581,7 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
std::unique_ptr<char[]> prefix; std::unique_ptr<char[]> prefix;
// check if this is a VFS provider // check if this is a VFS provider
if (!std::strcmp(mjVFS_PREFIX, provider->prefix)) { if (PrefixesAreIdentical(mjVFS_PREFIX, provider->prefix)) {
vfs_provider = true; vfs_provider = true;
} }
@@ -589,13 +626,8 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
} }
if (!vfs_provider && existing.prefix != nullptr) { if (!vfs_provider && existing.prefix != nullptr) {
int n = std::strlen(provider->prefix); // if identical then return slot number
int m = std::strlen(existing.prefix); if (PrefixesAreIdentical(provider->prefix, 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 (ResourceProvidersAreIdentical(provider, &existing)) { if (ResourceProvidersAreIdentical(provider, &existing)) {
return i; return i;
} else { } else {
@@ -691,20 +723,34 @@ int mjp_resourceProviderCount() {
return GetGlobal<mjpResourceProvider>().count().load(std::memory_order_acquire); 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 mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
const int count = mjp_resourceProviderCount(); const int count = mjp_resourceProviderCount();
if (!resource_name || !resource_name[0]) { if (!resource_name || !resource_name[0]) {
return nullptr; 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 // since multiple VFS resource providers can be registered with the same
// prefix, it doesn't make sense to try to match against them // 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; return nullptr;
} }
Global<mjpResourceProvider>& global = GetGlobal<mjpResourceProvider>(); Global<mjpResourceProvider>& global = GetGlobal<mjpResourceProvider>();
auto lock = global.lock_mutex_exclusively();
PluginTable<mjpResourceProvider>* table = &global.table(); PluginTable<mjpResourceProvider>* table = &global.table();
int found_slot = 0; int found_slot = 0;
@@ -717,7 +763,7 @@ const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
const char *prefix = provider.prefix; const char *prefix = provider.prefix;
if (prefix != nullptr && if (prefix != nullptr &&
!std::strncmp(prefix, resource_name, std::strlen(prefix))) { PrefixesAreIdentical(prefix, file_prefix.c_str())) {
return &provider; return &provider;
} }
} }
+55 -10
View File
@@ -30,6 +30,21 @@ typedef struct {
int nbuffer; int nbuffer;
} file_buffer; } 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 // open the given resource; if the name doesn't have a prefix matching with a
// resource provider, then the default_provider is used // 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 // find provider based off prefix of name
provider = mjp_getResourceProvider(name); provider = mjp_getResourceProvider(name);
if (provider != NULL) { if (provider != NULL) {
resource->read = provider->read; fillResource(provider, resource);
resource->close = provider->close;
resource->provider_data = provider->data;
if (provider->open(resource)) { if (provider->open(resource)) {
return resource; return resource;
} }
@@ -79,9 +92,8 @@ mjResource* mju_openResource(const char* name, int default_provider) {
mju_free(resource); mju_free(resource);
return NULL; return NULL;
} }
resource->read = provider->read;
resource->close = provider->close; fillResource(provider, resource);
resource->provider_data = provider->data;
if (provider->open(resource)) { if (provider->open(resource)) {
return resource; return resource;
} }
@@ -96,9 +108,7 @@ mjResource* mju_openResource(const char* name, int default_provider) {
// lastly fallback to OS filesystem // lastly fallback to OS filesystem
else { else {
resource->read = NULL; fillResource(NULL, resource);
resource->close = NULL;
resource->provider_data = NULL;
resource->data = mju_malloc(sizeof(file_buffer)); resource->data = mju_malloc(sizeof(file_buffer));
file_buffer* fb = (file_buffer*) resource->data; file_buffer* fb = (file_buffer*) resource->data;
fb->buffer = mju_fileToMemory(name, &(fb->nbuffer)); 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; const file_buffer* fb = (file_buffer*) resource->data;
*buffer = fb->buffer; *buffer = fb->buffer;
return fb->nbuffer; 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) // read file into memory buffer (allocated here with mju_malloc)
void* mju_fileToMemory(const char* filename, int* filesize) { void* mju_fileToMemory(const char* filename, int* filesize) {
// open file // open file
+6
View File
@@ -34,6 +34,12 @@ MJAPI void mju_closeResource(mjResource* resource);
// return negative value if error // return negative value if error
MJAPI int mju_readResource(mjResource* resource, const void** buffer); 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) // read file into memory buffer (allocated here with mju_malloc)
void* mju_fileToMemory(const char* filename, int* filesize); 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 // registers a VFS resource provider; returns the index of the provider
int mj_registerVfsProvider(const mjVFS* vfs) { int mj_registerVfsProvider(const mjVFS* vfs) {
mjpResourceProvider provider = { mjpResourceProvider provider = {
@@ -258,6 +270,7 @@ int mj_registerVfsProvider(const mjVFS* vfs) {
.open = &vfs_open_callback, .open = &vfs_open_callback,
.read = &vfs_read_callback, .read = &vfs_read_callback,
.close = &vfs_close_callback, .close = &vfs_close_callback,
.getdir = &vfs_getdir_callback,
.data = (void*) vfs .data = (void*) vfs
}; };
+4 -4
View File
@@ -176,7 +176,7 @@ template <typename T> static T* VecToArray(std::vector<T>& vector, bool clear =
// compiler // compiler
void mjCMesh::Compile(int default_provider) { void mjCMesh::Compile(int vfs_provider) {
// load file // load file
if (!file.empty()) { if (!file.empty()) {
// remove path from file if necessary // 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); string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, default_provider); mjResource* resource = LoadResource(filename, vfs_provider);
try { try {
if (!strcasecmp(ext.c_str(), ".stl")) { if (!strcasecmp(ext.c_str(), ".stl")) {
@@ -1535,7 +1535,7 @@ mjCSkin::~mjCSkin() {
// compiler // compiler
void mjCSkin::Compile(int default_provider) { void mjCSkin::Compile(int vfs_provider) {
// load file // load file
if (!file.empty()) { if (!file.empty()) {
@@ -1564,7 +1564,7 @@ void mjCSkin::Compile(int default_provider) {
} }
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, default_provider); mjResource* resource = LoadResource(filename, vfs_provider);
try { try {
LoadSKN(resource); LoadSKN(resource);
+7 -7
View File
@@ -2393,7 +2393,7 @@ static void warninghandler(const char* msg) {
// compiler // 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 // The volatile keyword is necessary to prevent a possible memory leak due to
// an interaction between longjmp and compiler optimization. Specifically, at // an interaction between longjmp and compiler optimization. Specifically, at
// the point where the setjmp takes places, these pointers have never been // 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. // TryCompile resulted in an mju_error which was converted to a longjmp.
throw mjCError(0, "engine error: %s", errortext); 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) { } catch (mjCError err) {
// deallocate everything allocated in Compile // deallocate everything allocated in Compile
mj_deleteModel(m); 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 // check if nan test works
double test = mjNAN; double test = mjNAN;
if (mjuu_defined(test)) { if (mjuu_defined(test)) {
@@ -2527,7 +2527,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int default_provider) {
// compile meshes (needed for geom compilation) // compile meshes (needed for geom compilation)
for (int i=0; i<meshes.size(); i++) { for (int i=0; i<meshes.size(); i++) {
meshes[i]->Compile(default_provider); meshes[i]->Compile(vfs_provider);
} }
// automatically set nuser fields // 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 // 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<skins.size(); i++) skins[i]->Compile(vfs_provider);
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(default_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(default_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<materials.size(); i++) materials[i]->Compile();
for (int i=0; i<pairs.size(); i++) pairs[i]->Compile(); for (int i=0; i<pairs.size(); i++) pairs[i]->Compile();
for (int i=0; i<excludes.size(); i++) excludes[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(); // constructor
~mjCModel(); // destructor ~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 bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back
void FuseStatic(void); // fuse static bodies with parent void FuseStatic(void); // fuse static bodies with parent
void FuseReindex(mjCBody* body); // reindex elements during fuse void FuseReindex(mjCBody* body); // reindex elements during fuse
@@ -163,8 +163,8 @@ class mjCModel {
int nuser_sensor; // number of mjtNums in sensor_user int nuser_sensor; // number of mjtNums in sensor_user
private: private:
void TryCompile(mjModel*& m, mjData*& d, int default_provider); void TryCompile(mjModel*& m, mjData*& d, int vfs_provider);
mjModel* _Compile(int default_provider); mjModel* _Compile(int vfs_provider);
void Clear(void); // clear objects allocated by Compile void Clear(void); // clear objects allocated by Compile
+14 -14
View File
@@ -2114,7 +2114,7 @@ void mjCHField::LoadPNG(mjResource* resource) {
// compiler // compiler
void mjCHField::Compile(int default_provider) { void mjCHField::Compile(int vfs_provider) {
// check size parameters // check size parameters
for (int i=0; i<4; i++) for (int i=0; i<4; i++)
if (size[i]<=0) if (size[i]<=0)
@@ -2136,7 +2136,7 @@ void mjCHField::Compile(int default_provider) {
// make filename // make filename
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); 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 // load depending on format
string ext = mjuu_getext(filename); string ext = mjuu_getext(filename);
@@ -2530,12 +2530,12 @@ void mjCTexture::LoadCustom(mjResource* resource,
// load from PNG or custom file, flip if specified // 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, std::vector<unsigned char>& image,
unsigned int& w, unsigned int& h) { unsigned int& w, unsigned int& h) {
// dispatch to PNG or Custom loaded // dispatch to PNG or Custom loaded
string ext = mjuu_getext(filename); string ext = mjuu_getext(filename);
mjResource* resource = LoadResource(filename, default_provider); mjResource* resource = LoadResource(filename, vfs_provider);
try { try {
if (!strcasecmp(ext.c_str(), ".png")) { if (!strcasecmp(ext.c_str(), ".png")) {
@@ -2597,11 +2597,11 @@ void mjCTexture::LoadFlip(string filename, int default_provider,
// load 2D // load 2D
void mjCTexture::Load2D(string filename, int default_provider) { void mjCTexture::Load2D(string filename, int vfs_provider) {
// load PNG or custom // load PNG or custom
unsigned int w, h; unsigned int w, h;
std::vector<unsigned char> image; std::vector<unsigned char> image;
LoadFlip(filename, default_provider, image, w, h); LoadFlip(filename, vfs_provider, image, w, h);
// assign size // assign size
width = w; width = w;
@@ -2620,7 +2620,7 @@ void mjCTexture::Load2D(string filename, int default_provider) {
// load cube or skybox from single file (repeated or grid) // 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 // check gridsize
if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) { if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) {
throw mjCError(this, throw mjCError(this,
@@ -2631,7 +2631,7 @@ void mjCTexture::LoadCubeSingle(string filename, int default_provider) {
// load PNG or custom // load PNG or custom
unsigned int w, h; unsigned int w, h;
std::vector<unsigned char> image; std::vector<unsigned char> image;
LoadFlip(filename, default_provider, image, w, h); LoadFlip(filename, vfs_provider, image, w, h);
// check gridsize for compatibility // check gridsize for compatibility
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) { 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 // 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 // keep track of which faces were defined
int loaded[6] = {0, 0, 0, 0, 0, 0}; int loaded[6] = {0, 0, 0, 0, 0, 0};
@@ -2738,7 +2738,7 @@ void mjCTexture::LoadCubeSeparate(int default_provider) {
// load PNG or custom // load PNG or custom
unsigned int w, h; unsigned int w, h;
std::vector<unsigned char> image; std::vector<unsigned char> image;
LoadFlip(filename, default_provider, image, w, h); LoadFlip(filename, vfs_provider, image, w, h);
// PNG must be square // PNG must be square
if (w!=h) { if (w!=h) {
@@ -2792,7 +2792,7 @@ void mjCTexture::LoadCubeSeparate(int default_provider) {
// compiler // compiler
void mjCTexture::Compile(int default_provider) { void mjCTexture::Compile(int vfs_provider) {
// builtin // builtin
if (builtin!=mjBUILTIN_NONE) { if (builtin!=mjBUILTIN_NONE) {
// check size // check size
@@ -2835,9 +2835,9 @@ void mjCTexture::Compile(int default_provider) {
// dispatch // dispatch
if (type==mjTEXTURE_2D) { if (type==mjTEXTURE_2D) {
Load2D(filename, default_provider); Load2D(filename, vfs_provider);
} else { } else {
LoadCubeSingle(filename, default_provider); LoadCubeSingle(filename, vfs_provider);
} }
} }
@@ -2865,7 +2865,7 @@ void mjCTexture::Compile(int default_provider) {
} }
// only cube and skybox // only cube and skybox
LoadCubeSeparate(default_provider); LoadCubeSeparate(vfs_provider);
} }
// make sure someone allocated data; SHOULD NOT OCCUR // make sure someone allocated data; SHOULD NOT OCCUR
+8 -8
View File
@@ -543,7 +543,7 @@ class mjCMesh: public mjCBase {
private: private:
mjCMesh(mjCModel* = 0, mjCDef* = 0); // constructor mjCMesh(mjCModel* = 0, mjCDef* = 0); // constructor
~mjCMesh(); // destructor ~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 LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format
void LoadSTL(mjResource* resource); // load mesh in STL BIN format void LoadSTL(mjResource* resource); // load mesh in STL BIN format
void LoadMSH(mjResource* resource); // load mesh in MSH BIN format void LoadMSH(mjResource* resource); // load mesh in MSH BIN format
@@ -625,7 +625,7 @@ class mjCSkin: public mjCBase {
private: private:
mjCSkin(mjCModel* = 0); // constructor mjCSkin(mjCModel* = 0); // constructor
~mjCSkin(); // destructor ~mjCSkin(); // destructor
void Compile(int default_provider); // compiler void Compile(int vfs_provider); // compiler
void LoadSKN(mjResource* resource); // load skin in SKN BIN format void LoadSKN(mjResource* resource); // load skin in SKN BIN format
int matid; // material id int matid; // material id
@@ -651,7 +651,7 @@ class mjCHField : public mjCBase {
private: private:
mjCHField(mjCModel* model); // constructor mjCHField(mjCModel* model); // constructor
~mjCHField(); // destructor ~mjCHField(); // destructor
void Compile(int default_provider); // compiler void Compile(int vfs_provider); // compiler
void LoadCustom(mjResource* resource); // load from custom format void LoadCustom(mjResource* resource); // load from custom format
void LoadPNG(mjResource* resource); // load from PNG format void LoadPNG(mjResource* resource); // load from PNG format
@@ -695,15 +695,15 @@ class mjCTexture : public mjCBase {
private: private:
mjCTexture(mjCModel*); // constructor mjCTexture(mjCModel*); // constructor
~mjCTexture(); // destructior ~mjCTexture(); // destructior
void Compile(int default_provider); // compiler void Compile(int vfs_provider); // compiler
void Builtin2D(void); // make builtin 2D void Builtin2D(void); // make builtin 2D
void BuiltinCube(void); // make builtin cube void BuiltinCube(void); // make builtin cube
void Load2D(std::string filename, int default_provider); // load 2D from file void Load2D(std::string filename, int vfs_provider); // load 2D from file
void LoadCubeSingle(std::string filename, int default_provider); // load cube from single file void LoadCubeSingle(std::string filename, int vfs_provider); // load cube from single file
void LoadCubeSeparate(int default_provider); // load cube from separate files 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, std::vector<unsigned char>& image,
unsigned int& w, unsigned int& h); unsigned int& w, unsigned int& h);
+7 -19
View File
@@ -22,6 +22,7 @@
#include <string_view> #include <string_view>
#include <mujoco/mjtnum.h> #include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "engine/engine_macro.h" #include "engine/engine_macro.h"
#include "engine/engine_util_spatial.h" #include "engine/engine_util_spatial.h"
@@ -558,6 +559,12 @@ bool mjuu_isabspath(string path) {
return false; 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 // check first char
const char* str = path.c_str(); const char* str = path.c_str();
if (str[0]=='\\' || str[0]=='/') { 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 // assemble full filename
string mjuu_makefullname(string filedir, string meshdir, string 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 // check if path is absolute
bool mjuu_isabspath(std::string path); bool mjuu_isabspath(std::string path);
// get path from filename
std::string mjuu_getfiledir(std::string filename);
// assemble full filename // assemble full filename
std::string mjuu_makefullname(std::string filedir, std::string meshdir, std::string 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 // find include elements recursively, replace them with subtree from xml file
static XMLElement* mjIncludeXML(XMLElement* elem, string dir, static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
int default_provider, vector<string>& included) { int vfs_provider, vector<string>& included) {
// include element: process // include element: process
if (!strcasecmp(elem->Value(), "include")) { if (!strcasecmp(elem->Value(), "include")) {
// make sure include has no children // make sure include has no children
@@ -152,9 +152,9 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
// get data source // get data source
mjResource *resource = nullptr; mjResource *resource = nullptr;
const char* xmlstring = 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 // 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()); 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 // 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 // otherwise check all child elements, return self
else { else {
XMLElement* child = elem->FirstChildElement(); XMLElement* child = elem->FirstChildElement();
while (child) { while (child) {
child = mjIncludeXML(child, dir, default_provider, included); child = mjIncludeXML(child, dir, vfs_provider, included);
if (child) { if (child) {
child = child->NextSiblingElement(); child = child->NextSiblingElement();
} }
@@ -234,7 +234,7 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
// Main parser function // 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; LocaleOverride locale_override;
// check arguments // check arguments
@@ -254,9 +254,9 @@ mjCModel* mjParseXML(const char* filename, int default_provider, char* error, in
// get data source // get data source
mjResource* resource = nullptr; mjResource* resource = nullptr;
const char* xmlstring = 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 // load from OS filesystem
if (!default_provider || (resource = mju_openResource(filename, 0)) == nullptr) { if (!vfs_provider || (resource = mju_openResource(filename, 0)) == nullptr) {
if (error) { if (error) {
snprintf(error, error_sz, "mjParseXML: could not open file '%s'", filename); 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; XMLDocument doc;
doc.Parse(xmlstring, buffer_size); doc.Parse(xmlstring, buffer_size);
// close resource
mju_closeResource(resource);
// error checking // error checking
if (doc.Error()) { if (doc.Error()) {
if (error) { if (error) {
snprintf(error, error_sz, "XML parse error %d:\n%s\n", snprintf(error, error_sz, "XML parse error %d:\n%s\n",
doc.ErrorID(), doc.ErrorStr()); doc.ErrorID(), doc.ErrorStr());
} }
mju_closeResource(resource);
return nullptr; return nullptr;
} }
// get top-level element // get top-level element
XMLElement* root = doc.RootElement(); XMLElement* root = doc.RootElement();
if (!root) { if (!root) {
mju_closeResource(resource);
mjCopyError(error, "XML root element not found", error_sz); mjCopyError(error, "XML root element not found", error_sz);
return nullptr; return nullptr;
} }
// create model, set filedir // create model, set filedir
model = new mjCModel; 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 // parse with exceptions
try { 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 // find include elements, replace them with subtree from xml file
vector<string> included; vector<string> included;
included.push_back(filename); included.push_back(filename);
mjIncludeXML(root, model->modelfiledir, default_provider, included); mjIncludeXML(root, model->modelfiledir, vfs_provider, included);
// parse MuJoCo model // parse MuJoCo model
mjXReader parser; mjXReader parser;
+1 -1
View File
@@ -26,7 +26,7 @@
bool mjWriteXML(mjCModel* model, std::string filename, char* error, int error_sz); bool mjWriteXML(mjCModel* model, std::string filename, char* error, int error_sz);
// Main parser function // 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_ #endif // MUJOCO_SRC_XML_XML_H_
+3 -3
View File
@@ -83,19 +83,19 @@ void mj_deactivate(void) {
// mj_loadXML helper function // 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) { char* error, int error_sz) {
// serialize access to themodel // serialize access to themodel
std::lock_guard<std::mutex> lock(themutex); std::lock_guard<std::mutex> lock(themutex);
// parse new model // parse new model
mjCModel* newmodel = mjParseXML(filename, default_provider, error, error_sz); mjCModel* newmodel = mjParseXML(filename, vfs_provider, error, error_sz);
if (!newmodel) { if (!newmodel) {
return nullptr; return nullptr;
} }
// compile new model // compile new model
mjModel* m = newmodel->Compile(default_provider); mjModel* m = newmodel->Compile(vfs_provider);
if (!m) { if (!m) {
mjCopyError(error, newmodel->GetError().message, error_sz); mjCopyError(error, newmodel->GetError().message, error_sz);
delete newmodel; delete newmodel;
+128 -36
View File
@@ -24,6 +24,7 @@
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "src/engine/engine_plugin.h" #include "src/engine/engine_plugin.h"
#include "src/engine/engine_resource.h" #include "src/engine/engine_resource.h"
#include "src/engine/engine_vfs.h"
#include "test/fixture.h" #include "test/fixture.h"
namespace mujoco { namespace mujoco {
@@ -41,7 +42,7 @@ int open_nop(mjResource* resource) {
} }
int open_str(mjResource* resource) { int open_str(mjResource* resource) {
if (std::strcmp(resource->name, "str://file")) { if (std::strcmp(resource->name, "str:file")) {
return 0; return 0;
} }
@@ -67,7 +68,7 @@ void close_str(mjResource* resource) {
} }
TEST_F(ResourceTest, RegisterProviderSuccess) { TEST_F(ResourceTest, RegisterProviderSuccess) {
mjpResourceProvider provider = {"myprefix", open_nop, read_nop, close_nop, mjpResourceProvider provider = {"my-prefix.123+45", open_nop, read_nop, close_nop,
nullptr}; nullptr};
int count1 = mjp_resourceProviderCount(); int count1 = mjp_resourceProviderCount();
@@ -97,7 +98,7 @@ TEST_F(ResourceTest, RegisterProviderMissingCallbacks) {
} }
TEST_F(ResourceTest, RegisterProviderMissingPrefix) { TEST_F(ResourceTest, RegisterProviderMissingPrefix) {
mjpResourceProvider provider = {"", open_nop, read_nop, close_nop, nullptr}; mjpResourceProvider provider = {"", open_nop, read_nop, close_nop, nullptr, nullptr};
// install warning handler // install warning handler
static char warning[1024]; static char warning[1024];
@@ -113,12 +114,8 @@ TEST_F(ResourceTest, RegisterProviderMissingPrefix) {
EXPECT_LT(i, 1); EXPECT_LT(i, 1);
} }
TEST_F(ResourceTest, RegisterProviderSubPrefix) { TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop, mjpResourceProvider provider = {"1invalid", open_nop, read_nop, close_nop, nullptr, nullptr};
nullptr};
mjpResourceProvider provider2 = {"pre", open_nop, read_nop, close_nop,
nullptr};
// install warning handler // install warning handler
static char warning[1024]; static char warning[1024];
@@ -128,21 +125,14 @@ TEST_F(ResourceTest, RegisterProviderSubPrefix) {
}; };
int i = mjp_registerResourceProvider(&provider); int i = mjp_registerResourceProvider(&provider);
int j = mjp_registerResourceProvider(&provider2);
// warning message related to missing prefix
// warning message related to an error EXPECT_THAT(warning, HasSubstr("prefix"));
EXPECT_THAT(warning, HasSubstr("cannot be register")); EXPECT_LT(i, 1);
EXPECT_GT(i, 0);
EXPECT_LT(j, 1);
} }
TEST_F(ResourceTest, RegisterProviderSuperPrefix) { TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop, mjpResourceProvider provider = {"invalid:", open_nop, read_nop, close_nop, nullptr, nullptr};
nullptr};
mjpResourceProvider provider2 = {"prefix2", open_nop, read_nop, close_nop,
nullptr};
// install warning handler // install warning handler
static char warning[1024]; static char warning[1024];
@@ -152,21 +142,18 @@ TEST_F(ResourceTest, RegisterProviderSuperPrefix) {
}; };
int i = mjp_registerResourceProvider(&provider); int i = mjp_registerResourceProvider(&provider);
int j = mjp_registerResourceProvider(&provider2);
// warning message related to missing prefix
// warning message related to an error EXPECT_THAT(warning, HasSubstr("prefix"));
EXPECT_THAT(warning, HasSubstr("cannot be register")); EXPECT_LT(i, 1);
EXPECT_GT(i, 0);
EXPECT_LT(j, 1);
} }
TEST_F(ResourceTest, RegisterProviderSame) { TEST_F(ResourceTest, RegisterProviderSame) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop, mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
nullptr}; nullptr, nullptr};
mjpResourceProvider provider2 = {"prefix", open_nop, read_nop, close_nop, mjpResourceProvider provider2 = {"prefix", open_nop, read_nop, close_nop,
nullptr}; nullptr, nullptr};
int i1 = mjp_registerResourceProvider(&provider); int i1 = mjp_registerResourceProvider(&provider);
int count1 = mjp_resourceProviderCount(); int count1 = mjp_resourceProviderCount();
@@ -176,19 +163,35 @@ TEST_F(ResourceTest, RegisterProviderSame) {
EXPECT_EQ(i1, i2); EXPECT_EQ(i1, i2);
EXPECT_EQ(count1, count2); EXPECT_EQ(count1, count2);
}
TEST_F(ResourceTest, RegisterProviderSameCase) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
nullptr, nullptr};
mjpResourceProvider provider2 = {"PREFIX", open_nop, read_nop, close_nop,
nullptr, nullptr};
int i1 = mjp_registerResourceProvider(&provider);
int count1 = mjp_resourceProviderCount();
int i2 = mjp_registerResourceProvider(&provider2);
int count2 = mjp_resourceProviderCount();
EXPECT_EQ(i1, i2);
EXPECT_EQ(count1, count2);
} }
TEST_F(ResourceTest, GeneralTest) { TEST_F(ResourceTest, GeneralTest) {
mjpResourceProvider provider = {"str://", open_str, read_str, close_str, mjpResourceProvider provider = {"str", open_str, read_str, close_str,
nullptr}; nullptr, nullptr};
// register resource provider // register resource provider
int i = mjp_registerResourceProvider(&provider); int i = mjp_registerResourceProvider(&provider);
EXPECT_GT(i, 0); EXPECT_GT(i, 0);
// open resource // open resource
mjResource* resource = mju_openResource("str://file", 0); mjResource* resource = mju_openResource("str:file", 0);
ASSERT_THAT(resource, NotNull()); ASSERT_THAT(resource, NotNull());
const char* buffer = NULL; const char* buffer = NULL;
@@ -200,8 +203,8 @@ TEST_F(ResourceTest, GeneralTest) {
} }
TEST_F(ResourceTest, GeneralTestFailure) { TEST_F(ResourceTest, GeneralTestFailure) {
mjpResourceProvider provider = {"str://", open_str, read_str, close_str, mjpResourceProvider provider = {"str", open_str, read_str, close_str,
nullptr}; nullptr, nullptr};
// register resource provider // register resource provider
int i = mjp_registerResourceProvider(&provider); int i = mjp_registerResourceProvider(&provider);
@@ -216,11 +219,100 @@ TEST_F(ResourceTest, GeneralTestFailure) {
}; };
// open resource // open resource
mjResource* resource = mju_openResource("str://notfound", 0); mjResource* resource = mju_openResource("str:notfound", 0);
ASSERT_THAT(resource, IsNull()); ASSERT_THAT(resource, IsNull());
EXPECT_THAT(warning, HasSubstr("could not open")); EXPECT_THAT(warning, HasSubstr("could not open"));
} }
TEST_F(ResourceTest, NameWithValidPrefix) {
mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop,
nullptr, nullptr};
// register resource provider
int i = mjp_registerResourceProvider(&provider);
EXPECT_GT(i, 0);
// install warning handler
static char warning[1024];
warning[0] = '\0';
mju_user_warning = [](const char* msg) {
util::strcpy_arr(warning, msg);
};
// open resource
mjResource* resource = mju_openResource("nop:found", 0);
ASSERT_THAT(resource, NotNull());
mju_closeResource(resource);
}
TEST_F(ResourceTest, NameWithUpperCasePrefix) {
mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop,
nullptr, nullptr};
// register resource provider
int i = mjp_registerResourceProvider(&provider);
EXPECT_GT(i, 0);
// install warning handler
static char warning[1024];
warning[0] = '\0';
mju_user_warning = [](const char* msg) {
util::strcpy_arr(warning, msg);
};
// open resource
mjResource* resource = mju_openResource("NOP:found", 0);
ASSERT_THAT(resource, NotNull());
mju_closeResource(resource);
}
TEST_F(ResourceTest, NameWithInvalidPrefix) {
mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop,
nullptr, nullptr};
// register resource provider
int i = mjp_registerResourceProvider(&provider);
EXPECT_GT(i, 0);
// install warning handler
static char warning[1024];
warning[0] = '\0';
mju_user_warning = [](const char* msg) {
util::strcpy_arr(warning, msg);
};
// open resource
mjResource* resource = mju_openResource("nopfound", 0);
ASSERT_THAT(resource, IsNull());
}
TEST_F(ResourceTest, VFSProvider) {
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
mj_makeEmptyFileVFS(vfs.get(), "file", 1);
// register resource provider
int i = mj_registerVfsProvider(vfs.get());
EXPECT_GT(i, 0);
// install warning handler
static char warning[1024];
warning[0] = '\0';
mju_user_warning = [](const char* msg) {
util::strcpy_arr(warning, msg);
};
// open resource
mjResource* resource = mju_openResource("vfs:file", 0);
ASSERT_THAT(resource, IsNull());
mj_deleteFileVFS(vfs.get(), "file");
}
} // namespace } // namespace
} // namespace mujoco } // namespace mujoco
+13 -35
View File
@@ -68,48 +68,26 @@ const std::string GetModelPath(std::string_view path) {
return absl::StrCat("../model/", path); return absl::StrCat("../model/", path);
} }
int datastr_length(mjResource* resource) {
int len = strlen(resource->name)-1;
// string should have the format "S(data).ext"
for (; len > 2; len--) {
if (resource->name[len] == ')') {
return len - 2;
}
}
return -1;
}
int str_open_callback(mjResource* resource) {
if (datastr_length(resource) < 0) {
return 0;
}
resource->data = resource->name+2;
return 1;
}
int str_read_callback(mjResource* resource, const void** buffer) {
*buffer = resource->data;
return datastr_length(resource);
}
void str_close_callback(mjResource* resource) {
}
mjModel* LoadModelFromString(std::string_view xml, char* error, mjModel* LoadModelFromString(std::string_view xml, char* error,
int error_size, mjVFS* vfs) { int error_size, mjVFS* vfs) {
// register string resource provider if not registered before // register string resource provider if not registered before
if (mjp_getResourceProvider("S(") == nullptr) { if (mjp_getResourceProvider("LoadModelFromString:") == nullptr) {
mjpResourceProvider resourceProvider; mjpResourceProvider resourceProvider;
resourceProvider.prefix = "S("; mjp_defaultResourceProvider(&resourceProvider);
resourceProvider.open = str_open_callback; resourceProvider.prefix = "LoadModelFromString";
resourceProvider.read = str_read_callback; resourceProvider.open = +[](mjResource* resource) {
resourceProvider.close = str_close_callback; resource->data = &(resource->name[strlen("LoadModelFromString:")]);
return 1;
};
resourceProvider.read = +[](mjResource* resource, const void** buffer) {
*buffer = resource->data;
return (int) strlen((const char*) resource->data);
};
resourceProvider.close = +[](mjResource* resource) {};
mjp_registerResourceProvider(&resourceProvider); mjp_registerResourceProvider(&resourceProvider);
} }
std::string xml2 = {xml.begin(), xml.end()}; std::string xml2 = {xml.begin(), xml.end()};
std::string str = "S(" + xml2 + ")"; std::string str = "LoadModelFromString:" + xml2;
return mj_loadXML(str.c_str(), vfs, error, error_size); return mj_loadXML(str.c_str(), vfs, error, error_size);
} }