Add various improvements for resource providers.
PiperOrigin-RevId: 529706437 Change-Id: I20385e031446674584349c301982dbe13b812477
This commit is contained in:
committed by
Copybara-Service
parent
b798f89212
commit
67f0f5154f
@@ -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
@@ -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>`.
|
||||
|
||||
|
||||
@@ -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_ {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user