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
.. _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
@@ -960,7 +971,15 @@ triggered by the compiler and the engine during various phases of the computatio
.. 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:
@@ -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
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:
+2 -1
View File
@@ -26,6 +26,7 @@ General
- 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.
- 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)
------------------------------
@@ -66,7 +67,7 @@ General
#. Added midphase and broadphase collision statistics to :ref:`mjData`.
#. Added documentation for :ref:`engine plugins<exPlugin>`.
#. 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
the :ref:`Virtual file system<Virtualfilesystem>`.
+9 -5
View File
@@ -624,6 +624,9 @@ struct mjResource_ {
// closing callback from resource provider
void (*close)(struct mjResource_* resource);
// getdir callback from resource provider
void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir);
};
typedef struct mjResource_ mjResource;
struct mjOption_ { // physics options
@@ -1198,11 +1201,12 @@ struct mjModel_ {
};
typedef struct mjModel_ mjModel;
struct mjpResourceProvider_ {
const char* prefix; // prefix for match against a resource name
mjfOpenResource open; // opening callback
mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback
void* data; // opaque data pointer (resource invariant)
const char* prefix; // prefix for match against a resource name
mjfOpenResource open; // opening callback
mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback
mjfGetResourceDir getdir; // getdir callback (optional)
void* data; // opaque data pointer (resource invariant)
};
typedef struct mjpResourceProvider_ mjpResourceProvider;
typedef enum mjtPluginCapabilityBit_ {
+129 -2
View File
@@ -3,8 +3,8 @@
Extensions
----------
This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided only
via **engine plugins**.
This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided by
via :ref:`engine plugins<exPlugin>` and :ref:`resource providers<exProvider>`.
.. _exPlugin:
@@ -246,3 +246,130 @@ A future version of this section will include:
* 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
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
void (*close)(struct mjResource_* resource);
// getdir callback from resource provider
void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir);
};
typedef struct mjResource_ mjResource;
+11 -6
View File
@@ -22,7 +22,7 @@
//---------------------------------- 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
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)
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 mjpResourceProvider_ {
const char* prefix; // prefix for match against a resource name
mjfOpenResource open; // opening callback
mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback
void* data; // opaque data pointer (resource invariant)
const char* prefix; // prefix for match against a resource name
mjfOpenResource open; // opening callback
mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback
mjfGetResourceDir getdir; // getdir callback (optional)
void* data; // opaque data pointer (resource invariant)
};
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
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;
+128 -36
View File
@@ -24,6 +24,7 @@
#include <mujoco/mujoco.h>
#include "src/engine/engine_plugin.h"
#include "src/engine/engine_resource.h"
#include "src/engine/engine_vfs.h"
#include "test/fixture.h"
namespace mujoco {
@@ -41,7 +42,7 @@ int open_nop(mjResource* resource) {
}
int open_str(mjResource* resource) {
if (std::strcmp(resource->name, "str://file")) {
if (std::strcmp(resource->name, "str:file")) {
return 0;
}
@@ -67,7 +68,7 @@ void close_str(mjResource* resource) {
}
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};
int count1 = mjp_resourceProviderCount();
@@ -97,7 +98,7 @@ TEST_F(ResourceTest, RegisterProviderMissingCallbacks) {
}
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
static char warning[1024];
@@ -113,12 +114,8 @@ TEST_F(ResourceTest, RegisterProviderMissingPrefix) {
EXPECT_LT(i, 1);
}
TEST_F(ResourceTest, RegisterProviderSubPrefix) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
nullptr};
mjpResourceProvider provider2 = {"pre", open_nop, read_nop, close_nop,
nullptr};
TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) {
mjpResourceProvider provider = {"1invalid", open_nop, read_nop, close_nop, nullptr, nullptr};
// install warning handler
static char warning[1024];
@@ -128,21 +125,14 @@ TEST_F(ResourceTest, RegisterProviderSubPrefix) {
};
int i = mjp_registerResourceProvider(&provider);
int j = mjp_registerResourceProvider(&provider2);
// warning message related to an error
EXPECT_THAT(warning, HasSubstr("cannot be register"));
EXPECT_GT(i, 0);
EXPECT_LT(j, 1);
// warning message related to missing prefix
EXPECT_THAT(warning, HasSubstr("prefix"));
EXPECT_LT(i, 1);
}
TEST_F(ResourceTest, RegisterProviderSuperPrefix) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
nullptr};
mjpResourceProvider provider2 = {"prefix2", open_nop, read_nop, close_nop,
nullptr};
TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) {
mjpResourceProvider provider = {"invalid:", open_nop, read_nop, close_nop, nullptr, nullptr};
// install warning handler
static char warning[1024];
@@ -152,21 +142,18 @@ TEST_F(ResourceTest, RegisterProviderSuperPrefix) {
};
int i = mjp_registerResourceProvider(&provider);
int j = mjp_registerResourceProvider(&provider2);
// warning message related to an error
EXPECT_THAT(warning, HasSubstr("cannot be register"));
EXPECT_GT(i, 0);
EXPECT_LT(j, 1);
// warning message related to missing prefix
EXPECT_THAT(warning, HasSubstr("prefix"));
EXPECT_LT(i, 1);
}
TEST_F(ResourceTest, RegisterProviderSame) {
mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
nullptr};
nullptr, nullptr};
mjpResourceProvider provider2 = {"prefix", open_nop, read_nop, close_nop,
nullptr};
nullptr, nullptr};
int i1 = mjp_registerResourceProvider(&provider);
int count1 = mjp_resourceProviderCount();
@@ -176,19 +163,35 @@ TEST_F(ResourceTest, RegisterProviderSame) {
EXPECT_EQ(i1, i2);
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) {
mjpResourceProvider provider = {"str://", open_str, read_str, close_str,
nullptr};
mjpResourceProvider provider = {"str", open_str, read_str, close_str,
nullptr, nullptr};
// register resource provider
int i = mjp_registerResourceProvider(&provider);
EXPECT_GT(i, 0);
// open resource
mjResource* resource = mju_openResource("str://file", 0);
mjResource* resource = mju_openResource("str:file", 0);
ASSERT_THAT(resource, NotNull());
const char* buffer = NULL;
@@ -200,8 +203,8 @@ TEST_F(ResourceTest, GeneralTest) {
}
TEST_F(ResourceTest, GeneralTestFailure) {
mjpResourceProvider provider = {"str://", open_str, read_str, close_str,
nullptr};
mjpResourceProvider provider = {"str", open_str, read_str, close_str,
nullptr, nullptr};
// register resource provider
int i = mjp_registerResourceProvider(&provider);
@@ -216,11 +219,100 @@ TEST_F(ResourceTest, GeneralTestFailure) {
};
// open resource
mjResource* resource = mju_openResource("str://notfound", 0);
ASSERT_THAT(resource, IsNull());
mjResource* resource = mju_openResource("str:notfound", 0);
ASSERT_THAT(resource, IsNull());
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 mujoco
+13 -35
View File
@@ -68,48 +68,26 @@ const std::string GetModelPath(std::string_view 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,
int error_size, mjVFS* vfs) {
// register string resource provider if not registered before
if (mjp_getResourceProvider("S(") == nullptr) {
if (mjp_getResourceProvider("LoadModelFromString:") == nullptr) {
mjpResourceProvider resourceProvider;
resourceProvider.prefix = "S(";
resourceProvider.open = str_open_callback;
resourceProvider.read = str_read_callback;
resourceProvider.close = str_close_callback;
mjp_defaultResourceProvider(&resourceProvider);
resourceProvider.prefix = "LoadModelFromString";
resourceProvider.open = +[](mjResource* resource) {
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);
}
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);
}