Add a new plugin / extension mechanism called a resource provider along with retrofitting VFS on top of it.
A resource provider provides a mechanism for MuJoCo to read from filesystems other than the OS filesystem or the Virtual File System (VFS). PiperOrigin-RevId: 525394983 Change-Id: I077ff5a7e2e76806b48b6defb531280aadc8b169
This commit is contained in:
committed by
Copybara-Service
parent
b25728cc2e
commit
fe3dccfd1d
@@ -3069,3 +3069,52 @@ mjp_getPluginAtSlot
|
|||||||
|
|
||||||
Look up a plugin by the registered slot number that was returned by mjp_registerPlugin.
|
Look up a plugin by the registered slot number that was returned by mjp_registerPlugin.
|
||||||
|
|
||||||
|
.. _mjp_defaultResourceProvider:
|
||||||
|
|
||||||
|
mjp_defaultResourceProvider
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjp_defaultResourceProvider
|
||||||
|
|
||||||
|
Set default resource provider definition.
|
||||||
|
|
||||||
|
.. _mjp_registerResourceProvider:
|
||||||
|
|
||||||
|
mjp_registerResourceProvider
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjp_registerResourceProvider
|
||||||
|
|
||||||
|
Globally register a resource provider in a thread-safe manner. The provider must have a prefix
|
||||||
|
that is not a sub-prefix or super-prefix of any current registered providers. This function
|
||||||
|
returns a slot number > 0 on success.
|
||||||
|
|
||||||
|
.. _mjp_resourceProviderCount:
|
||||||
|
|
||||||
|
mjp_resourceProviderCount
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjp_resourceProviderCount
|
||||||
|
|
||||||
|
Return the number of globally registered resource providers.
|
||||||
|
|
||||||
|
.. _mjp_getResourceProvider:
|
||||||
|
|
||||||
|
mjp_getResourceProvider
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjp_getResourceProvider
|
||||||
|
|
||||||
|
Return the resource provider with the prefix that matches against the resource name.
|
||||||
|
If no match, return NULL.
|
||||||
|
|
||||||
|
.. _mjp_getResourceProviderAtSlot:
|
||||||
|
|
||||||
|
mjp_getResourceProviderAtSlot
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjp_getResourceProviderAtSlot
|
||||||
|
|
||||||
|
Look up a resource provider by slot number returned by mjp_registerResourceProvider.
|
||||||
|
If invalid slot number, return NULL.
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ General
|
|||||||
equivalent body inertias with ellipsoids instead of the default boxes.
|
equivalent body inertias with ellipsoids instead of the default boxes.
|
||||||
- Added documentation for :ref:`engine plugins<exPlugin>`.
|
- Added documentation for :ref:`engine plugins<exPlugin>`.
|
||||||
- Added struct information to the ``introspect`` module.
|
- Added struct information to the ``introspect`` module.
|
||||||
|
- Added a new extension mechanism called "resource provider" . This extensible mechanism allows MuJoCo
|
||||||
|
to read assets from data sources other than the local OS filesystem or
|
||||||
|
the :ref:`Virtual file system<Virtualfilesystem>`.
|
||||||
|
|
||||||
Python bindings
|
Python bindings
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -607,13 +607,25 @@ struct mjLROpt_ { // options for mj_setLengthRange()
|
|||||||
mjtNum tolrange; // convergence tolerance (relative to range)
|
mjtNum tolrange; // convergence tolerance (relative to range)
|
||||||
};
|
};
|
||||||
typedef struct mjLROpt_ mjLROpt;
|
typedef struct mjLROpt_ mjLROpt;
|
||||||
struct mjVFS_ { // virtual file system for loading from memory
|
struct mjVFS_ { // virtual file system for loading from memory
|
||||||
int nfile; // number of files present
|
int nfile; // number of files present
|
||||||
char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
|
char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
|
||||||
int filesize[mjMAXVFS]; // file size in bytes
|
int filesize[mjMAXVFS]; // file size in bytes
|
||||||
void* filedata[mjMAXVFS]; // buffer with file data
|
void* filedata[mjMAXVFS]; // buffer with file data
|
||||||
};
|
};
|
||||||
typedef struct mjVFS_ mjVFS;
|
typedef struct mjVFS_ mjVFS;
|
||||||
|
struct mjResource_ {
|
||||||
|
char* name; // name of resource (filename, etc)
|
||||||
|
void* data; // opaque data pointer
|
||||||
|
const void* provider_data; // opaque resource provider data
|
||||||
|
|
||||||
|
// reading callback from resource provider
|
||||||
|
int (*read)(struct mjResource_* resource, const void** buffer);
|
||||||
|
|
||||||
|
// closing callback from resource provider
|
||||||
|
void (*close)(struct mjResource_* resource);
|
||||||
|
};
|
||||||
|
typedef struct mjResource_ mjResource;
|
||||||
struct mjOption_ { // physics options
|
struct mjOption_ { // physics options
|
||||||
// timing parameters
|
// timing parameters
|
||||||
mjtNum timestep; // timestep
|
mjtNum timestep; // timestep
|
||||||
@@ -1186,6 +1198,14 @@ struct mjModel_ {
|
|||||||
int* names_map; // internal hash map of names (nnames_map x 1)
|
int* names_map; // internal hash map of names (nnames_map x 1)
|
||||||
};
|
};
|
||||||
typedef struct mjModel_ 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)
|
||||||
|
};
|
||||||
|
typedef struct mjpResourceProvider_ mjpResourceProvider;
|
||||||
typedef enum mjtPluginCapabilityBit_ {
|
typedef enum mjtPluginCapabilityBit_ {
|
||||||
mjPLUGIN_ACTUATOR = 1<<0,
|
mjPLUGIN_ACTUATOR = 1<<0,
|
||||||
mjPLUGIN_SENSOR = 1<<1,
|
mjPLUGIN_SENSOR = 1<<1,
|
||||||
@@ -2197,4 +2217,9 @@ int mjp_registerPlugin(const mjpPlugin* plugin);
|
|||||||
int mjp_pluginCount();
|
int mjp_pluginCount();
|
||||||
const mjpPlugin* mjp_getPlugin(const char* name, int* slot);
|
const mjpPlugin* mjp_getPlugin(const char* name, int* slot);
|
||||||
const mjpPlugin* mjp_getPluginAtSlot(int slot);
|
const mjpPlugin* mjp_getPluginAtSlot(int slot);
|
||||||
|
void mjp_defaultResourceProvider(mjpResourceProvider* provider);
|
||||||
|
int mjp_registerResourceProvider(const mjpResourceProvider* provider);
|
||||||
|
int mjp_resourceProviderCount();
|
||||||
|
const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name);
|
||||||
|
const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot);
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|||||||
@@ -369,15 +369,31 @@ typedef struct mjLROpt_ mjLROpt;
|
|||||||
|
|
||||||
//---------------------------------- mjVFS ---------------------------------------------------------
|
//---------------------------------- mjVFS ---------------------------------------------------------
|
||||||
|
|
||||||
struct mjVFS_ { // virtual file system for loading from memory
|
struct mjVFS_ { // virtual file system for loading from memory
|
||||||
int nfile; // number of files present
|
int nfile; // number of files present
|
||||||
char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
|
char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
|
||||||
int filesize[mjMAXVFS]; // file size in bytes
|
int filesize[mjMAXVFS]; // file size in bytes
|
||||||
void* filedata[mjMAXVFS]; // buffer with file data
|
void* filedata[mjMAXVFS]; // buffer with file data
|
||||||
};
|
};
|
||||||
typedef struct mjVFS_ mjVFS;
|
typedef struct mjVFS_ mjVFS;
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------- mjResource ----------------------------------------------------
|
||||||
|
|
||||||
|
struct mjResource_ {
|
||||||
|
char* name; // name of resource (filename, etc)
|
||||||
|
void* data; // opaque data pointer
|
||||||
|
const void* provider_data; // opaque resource provider data
|
||||||
|
|
||||||
|
// reading callback from resource provider
|
||||||
|
int (*read)(struct mjResource_* resource, const void** buffer);
|
||||||
|
|
||||||
|
// closing callback from resource provider
|
||||||
|
void (*close)(struct mjResource_* resource);
|
||||||
|
};
|
||||||
|
typedef struct mjResource_ mjResource;
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------- mjOption ------------------------------------------------------
|
//---------------------------------- mjOption ------------------------------------------------------
|
||||||
|
|
||||||
struct mjOption_ { // physics options
|
struct mjOption_ { // physics options
|
||||||
|
|||||||
@@ -20,6 +20,33 @@
|
|||||||
#include <mujoco/mjvisualize.h>
|
#include <mujoco/mjvisualize.h>
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------- Resource Provider ---------------------------------------------
|
||||||
|
|
||||||
|
#define mjVFS_PREFIX "vfs://" // prefix for VFS providers
|
||||||
|
|
||||||
|
// callback for opeing a resource, returns zero on failure
|
||||||
|
typedef int (*mjfOpenResource)(mjResource* resource);
|
||||||
|
|
||||||
|
// callback for reading a resource
|
||||||
|
// return number of bytes stored in buffer, return -1 if error
|
||||||
|
typedef int (*mjfReadResource)(mjResource* resource, const void** buffer);
|
||||||
|
|
||||||
|
// callback for closing a resource (responsible for freeing any allocated memory)
|
||||||
|
typedef void (*mjfCloseResource)(mjResource* resource);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
};
|
||||||
|
typedef struct mjpResourceProvider_ mjpResourceProvider;
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------- Plugins -------------------------------------------------------
|
||||||
|
|
||||||
typedef enum mjtPluginCapabilityBit_ {
|
typedef enum mjtPluginCapabilityBit_ {
|
||||||
mjPLUGIN_ACTUATOR = 1<<0,
|
mjPLUGIN_ACTUATOR = 1<<0,
|
||||||
mjPLUGIN_SENSOR = 1<<1,
|
mjPLUGIN_SENSOR = 1<<1,
|
||||||
|
|||||||
@@ -1192,6 +1192,25 @@ MJAPI const mjpPlugin* mjp_getPlugin(const char* name, int* slot);
|
|||||||
// Look up a plugin by the registered slot number that was returned by mjp_registerPlugin.
|
// Look up a plugin by the registered slot number that was returned by mjp_registerPlugin.
|
||||||
MJAPI const mjpPlugin* mjp_getPluginAtSlot(int slot);
|
MJAPI const mjpPlugin* mjp_getPluginAtSlot(int slot);
|
||||||
|
|
||||||
|
// Set default resource provider definition.
|
||||||
|
MJAPI void mjp_defaultResourceProvider(mjpResourceProvider* provider);
|
||||||
|
|
||||||
|
// Globally register a resource provider in a thread-safe manner. The provider must have a prefix
|
||||||
|
// that is not a sub-prefix or super-prefix of any current registered providers. This function
|
||||||
|
// returns a slot number > 0 on success.
|
||||||
|
MJAPI int mjp_registerResourceProvider(const mjpResourceProvider* provider);
|
||||||
|
|
||||||
|
// Return the number of globally registered resource providers.
|
||||||
|
MJAPI int mjp_resourceProviderCount();
|
||||||
|
|
||||||
|
// Return the resource provider with the prefix that matches against the resource name.
|
||||||
|
// If no match, return NULL.
|
||||||
|
MJAPI const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name);
|
||||||
|
|
||||||
|
// Look up a resource provider by slot number returned by mjp_registerResourceProvider.
|
||||||
|
// If invalid slot number, return NULL.
|
||||||
|
MJAPI const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot);
|
||||||
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ _ANONYMOUS_KEY_PATTERN = re.compile(r'\d+:\d+(?=\))')
|
|||||||
_EXCLUDED = (
|
_EXCLUDED = (
|
||||||
'mjpPlugin',
|
'mjpPlugin',
|
||||||
'mjpPlugin_',
|
'mjpPlugin_',
|
||||||
|
'mjpResourceProvider',
|
||||||
|
'mjpResourceProvider_',
|
||||||
|
'mjResource',
|
||||||
|
'mjResource_',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7374,4 +7374,69 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
|||||||
),
|
),
|
||||||
doc='Look up a plugin by the registered slot number that was returned by mjp_registerPlugin.', # pylint: disable=line-too-long
|
doc='Look up a plugin by the registered slot number that was returned by mjp_registerPlugin.', # pylint: disable=line-too-long
|
||||||
)),
|
)),
|
||||||
|
('mjp_defaultResourceProvider',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjp_defaultResourceProvider',
|
||||||
|
return_type=ValueType(name='void'),
|
||||||
|
parameters=(
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='provider',
|
||||||
|
type=PointerType(
|
||||||
|
inner_type=ValueType(name='mjpResourceProvider'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
doc='Set default resource provider definition.',
|
||||||
|
)),
|
||||||
|
('mjp_registerResourceProvider',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjp_registerResourceProvider',
|
||||||
|
return_type=ValueType(name='int'),
|
||||||
|
parameters=(
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='provider',
|
||||||
|
type=PointerType(
|
||||||
|
inner_type=ValueType(name='mjpResourceProvider', is_const=True), # pylint: disable=line-too-long
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
doc='Globally register a resource provider in a thread-safe manner. The provider must have a prefix that is not a sub-prefix or super-prefix of any current registered providers. This function returns a slot number > 0 on success.', # pylint: disable=line-too-long
|
||||||
|
)),
|
||||||
|
('mjp_resourceProviderCount',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjp_resourceProviderCount',
|
||||||
|
return_type=ValueType(name='int'),
|
||||||
|
parameters=(),
|
||||||
|
doc='Return the number of globally registered resource providers.',
|
||||||
|
)),
|
||||||
|
('mjp_getResourceProvider',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjp_getResourceProvider',
|
||||||
|
return_type=PointerType(
|
||||||
|
inner_type=ValueType(name='mjpResourceProvider', is_const=True),
|
||||||
|
),
|
||||||
|
parameters=(
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='resource_name',
|
||||||
|
type=PointerType(
|
||||||
|
inner_type=ValueType(name='char', is_const=True),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
doc='Return the resource provider with the prefix that matches against the resource name. If no match, return NULL.', # pylint: disable=line-too-long
|
||||||
|
)),
|
||||||
|
('mjp_getResourceProviderAtSlot',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjp_getResourceProviderAtSlot',
|
||||||
|
return_type=PointerType(
|
||||||
|
inner_type=ValueType(name='mjpResourceProvider', is_const=True),
|
||||||
|
),
|
||||||
|
parameters=(
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='slot',
|
||||||
|
type=ValueType(name='int'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
doc='Look up a resource provider by slot number returned by mjp_registerResourceProvider. If invalid slot number, return NULL.', # pylint: disable=line-too-long
|
||||||
|
)),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ set(MUJOCO_ENGINE_SRCS
|
|||||||
engine_derivative.h
|
engine_derivative.h
|
||||||
engine_derivative_fd.c
|
engine_derivative_fd.c
|
||||||
engine_derivative_fd.h
|
engine_derivative_fd.h
|
||||||
engine_file.c
|
|
||||||
engine_file.h
|
|
||||||
engine_forward.c
|
engine_forward.c
|
||||||
engine_forward.h
|
engine_forward.h
|
||||||
engine_inverse.c
|
engine_inverse.c
|
||||||
@@ -50,6 +48,8 @@ set(MUJOCO_ENGINE_SRCS
|
|||||||
engine_print.h
|
engine_print.h
|
||||||
engine_ray.c
|
engine_ray.c
|
||||||
engine_ray.h
|
engine_ray.h
|
||||||
|
engine_resource.c
|
||||||
|
engine_resource.h
|
||||||
engine_sensor.c
|
engine_sensor.c
|
||||||
engine_sensor.h
|
engine_sensor.h
|
||||||
engine_setconst.c
|
engine_setconst.c
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
// Copyright 2022 DeepMind Technologies Limited
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
#include "engine/engine_file.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include "engine/engine_util_errmem.h"
|
|
||||||
|
|
||||||
void* mju_fileToMemory(const char* filename, int* filesize) {
|
|
||||||
// open file
|
|
||||||
*filesize = 0;
|
|
||||||
FILE* fp = fopen(filename, "rb");
|
|
||||||
if (!fp) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find size
|
|
||||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Failed to calculate size for '%s'", filename);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure file size fits in int
|
|
||||||
long long_filesize = ftell(fp); // NOLINT(runtime/int)
|
|
||||||
if (long_filesize > INT_MAX) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("File size over 2GB is not supported. File: '%s'", filename);
|
|
||||||
return NULL;
|
|
||||||
} else if (long_filesize < 0) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Failed to calculate size for '%s'", filename);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
*filesize = long_filesize;
|
|
||||||
|
|
||||||
// go back to start of file
|
|
||||||
if (fseek(fp, 0, SEEK_SET) != 0) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Read error while reading '%s'", filename);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate and read
|
|
||||||
void* buffer = mju_malloc(*filesize);
|
|
||||||
if (!buffer) {
|
|
||||||
mju_error("mjFileToMemory: could not allocate memory");
|
|
||||||
}
|
|
||||||
size_t bytes_read = fread(buffer, 1, *filesize, fp);
|
|
||||||
|
|
||||||
// check that read data matches file size
|
|
||||||
if (bytes_read != *filesize) { // SHOULD NOT OCCUR
|
|
||||||
if (ferror(fp)) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_free(buffer);
|
|
||||||
*filesize = 0;
|
|
||||||
mju_warning("Read error while reading '%s'", filename);
|
|
||||||
return NULL;
|
|
||||||
} else if (feof(fp)) {
|
|
||||||
*filesize = bytes_read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close file, return contents
|
|
||||||
fclose(fp);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
+67
-105
@@ -25,6 +25,7 @@
|
|||||||
#include <mujoco/mjplugin.h>
|
#include <mujoco/mjplugin.h>
|
||||||
#include <mujoco/mjxmacro.h>
|
#include <mujoco/mjxmacro.h>
|
||||||
#include "engine/engine_array_safety.h"
|
#include "engine/engine_array_safety.h"
|
||||||
|
#include "engine/engine_resource.h"
|
||||||
#include "engine/engine_macro.h"
|
#include "engine/engine_macro.h"
|
||||||
#include "engine/engine_plugin.h"
|
#include "engine/engine_plugin.h"
|
||||||
#include "engine/engine_util_blas.h"
|
#include "engine/engine_util_blas.h"
|
||||||
@@ -620,82 +621,63 @@ void mj_saveModel(const mjModel* m, const char* filename, void* buffer, int buff
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// load model from binary MJB file
|
// load model from binary MJB resource
|
||||||
// if vfs is not NULL, look up file in vfs before reading from disk
|
static mjModel* _mj_loadModel(const char* filename, int default_provider) {
|
||||||
mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
|
|
||||||
int header[4] = {0};
|
int header[4] = {0};
|
||||||
int expected_header[4] = {ID, sizeof(mjtNum), getnint(), getnptr()};
|
int expected_header[4] = {ID, sizeof(mjtNum), getnint(), getnptr()};
|
||||||
int info[2000];
|
int info[2000];
|
||||||
int ptrbuf = 0;
|
int ptrbuf = 0;
|
||||||
mjModel *m = 0;
|
mjModel *m = 0;
|
||||||
FILE* fp = 0;
|
mjResource* r = NULL;
|
||||||
|
|
||||||
|
if((r = mju_openResource(filename, default_provider)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// find file in VFS if given
|
|
||||||
const void* buffer = NULL;
|
const void* buffer = NULL;
|
||||||
int buffer_sz = 0;
|
int buffer_sz = mju_readResource(r, &buffer);
|
||||||
if (vfs) {
|
if (buffer_sz <= 0) {
|
||||||
int i = mj_findFileVFS(vfs, filename);
|
mju_closeResource(r);
|
||||||
if (i>=0) {
|
return NULL;
|
||||||
buffer_sz = vfs->filesize[i];
|
|
||||||
buffer = vfs->filedata[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// open file for reading if no buffer
|
if (buffer_sz < 4*sizeof(int)) {
|
||||||
if (!buffer) {
|
mju_warning("Model file has an incomplete header");
|
||||||
fp = fopen(filename, "rb");
|
mju_closeResource(r);
|
||||||
if (!fp) {
|
return NULL;
|
||||||
mju_warning("Could not open file '%s'", filename);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read header
|
bufread(header, 4*sizeof(int), buffer_sz, buffer, &ptrbuf);
|
||||||
if (fp) {
|
|
||||||
if (fread(header, 4, sizeof(int), fp) != 4) {
|
|
||||||
mju_warning("Model file has an incomplete header");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bufread(header, 4*sizeof(int), buffer_sz, buffer, &ptrbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check header
|
// check header
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
if (header[i]!=expected_header[i]) {
|
if (header[i]!=expected_header[i]) {
|
||||||
if (fp) {
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 0:
|
case 0:
|
||||||
mju_warning("Model missing header ID");
|
mju_warning("Model missing header ID");
|
||||||
return 0;
|
mju_closeResource(r);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
mju_warning("Model and executable have different floating point precision");
|
mju_warning("Model and executable have different floating point precision");
|
||||||
return 0;
|
mju_closeResource(r);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
mju_warning("Model and executable have different number of ints in mjModel");
|
mju_warning("Model and executable have different number of ints in mjModel");
|
||||||
return 0;
|
mju_closeResource(r);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
mju_warning("Model and executable have different number of pointers in mjModel");
|
mju_warning("Model and executable have different number of pointers in mjModel");
|
||||||
return 0;
|
mju_closeResource(r);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read mjModel structure: info only
|
// read mjModel structure: info only
|
||||||
if (fp) {
|
bufread(info, sizeof(int)*getnint(), buffer_sz, buffer, &ptrbuf);
|
||||||
if (fread(info, sizeof(int), getnint(), fp) != getnint()) {
|
|
||||||
mju_warning("Model file does not contain enough ints");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bufread(info, sizeof(int)*getnint(), buffer_sz, buffer, &ptrbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate new mjModel, check sizes
|
// allocate new mjModel, check sizes
|
||||||
m = mj_makeModel(info[0], info[1], info[2], info[3], info[4], info[5], info[6],
|
m = mj_makeModel(info[0], info[1], info[2], info[3], info[4], info[5], info[6],
|
||||||
@@ -707,90 +689,70 @@ mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
|
|||||||
info[42], info[43], info[44], info[45], info[46], info[47], info[48],
|
info[42], info[43], info[44], info[45], info[46], info[47], info[48],
|
||||||
info[49], info[50], info[51], info[52]);
|
info[49], info[50], info[51], info[52]);
|
||||||
if (!m || m->nbuffer!=info[getnint()-1]) {
|
if (!m || m->nbuffer!=info[getnint()-1]) {
|
||||||
if (fp) {
|
mju_closeResource(r);
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
mju_warning("Corrupted model, wrong size parameters");
|
mju_warning("Corrupted model, wrong size parameters");
|
||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set info fields
|
// set info fields
|
||||||
memcpy(m, info, sizeof(int)*getnint());
|
memcpy(m, info, sizeof(int)*getnint());
|
||||||
|
|
||||||
// read options and buffer
|
// read options and buffer
|
||||||
if (fp) {
|
bufread((void*)&m->opt, sizeof(mjOption), buffer_sz, buffer, &ptrbuf);
|
||||||
if (fread((void*)&m->opt, sizeof(mjOption), 1, fp) != 1) {
|
bufread((void*)&m->vis, sizeof(mjVisual), buffer_sz, buffer, &ptrbuf);
|
||||||
mju_warning("Model file does not have a complete mjOption");
|
bufread((void*)&m->stat, sizeof(mjStatistic), buffer_sz, buffer, &ptrbuf);
|
||||||
mj_deleteModel(m);
|
{
|
||||||
return 0;
|
MJMODEL_POINTERS_PREAMBLE(m)
|
||||||
}
|
#define X(type, name, nr, nc) \
|
||||||
if (fread((void*)&m->vis, sizeof(mjVisual), 1, fp) != 1) {
|
bufread(m->name, sizeof(type)*(m->nr)*(nc), buffer_sz, buffer, &ptrbuf);
|
||||||
mju_warning("Model file does not have a complete mjVisual");
|
MJMODEL_POINTERS
|
||||||
mj_deleteModel(m);
|
#undef X
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (fread((void*)&m->stat, sizeof(mjStatistic), 1, fp) != 1) {
|
|
||||||
mju_warning("Model file does not have a complete mjStatistic");
|
|
||||||
mj_deleteModel(m);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
MJMODEL_POINTERS_PREAMBLE(m)
|
|
||||||
#define X(type, name, nr, nc) \
|
|
||||||
if (fread(m->name, sizeof(type), (m->nr)*(nc), fp) != (m->nr)*(nc)) { \
|
|
||||||
mju_warning("Model file does not contain a large enough buffer"); \
|
|
||||||
mj_deleteModel(m); \
|
|
||||||
return 0; \
|
|
||||||
}
|
|
||||||
MJMODEL_POINTERS
|
|
||||||
#undef X
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bufread((void*)&m->opt, sizeof(mjOption), buffer_sz, buffer, &ptrbuf);
|
|
||||||
bufread((void*)&m->vis, sizeof(mjVisual), buffer_sz, buffer, &ptrbuf);
|
|
||||||
bufread((void*)&m->stat, sizeof(mjStatistic), buffer_sz, buffer, &ptrbuf);
|
|
||||||
{
|
|
||||||
MJMODEL_POINTERS_PREAMBLE(m)
|
|
||||||
#define X(type, name, nr, nc) \
|
|
||||||
bufread(m->name, sizeof(type)*(m->nr)*(nc), buffer_sz, buffer, &ptrbuf);
|
|
||||||
MJMODEL_POINTERS
|
|
||||||
#undef X
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure file size is correct
|
// make sure buffer is the correct size
|
||||||
if (fp) {
|
if (ptrbuf != buffer_sz) {
|
||||||
if (feof(fp)) {
|
mju_closeResource(r);
|
||||||
fclose(fp);
|
mju_warning("Model file is too large");
|
||||||
mju_warning("Model file is too small");
|
mj_deleteModel(m);
|
||||||
mj_deleteModel(m);
|
return NULL;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
char dummy;
|
|
||||||
if (fread(&dummy, 1, 1, fp) || !feof(fp)) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Model file is too large");
|
|
||||||
mj_deleteModel(m);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* validationError = mj_validateReferences(m);
|
const char* validationError = mj_validateReferences(m);
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
|
mju_closeResource(r);
|
||||||
mju_warning("%s", validationError);
|
mju_warning("%s", validationError);
|
||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fp) {
|
mju_closeResource(r);
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// load model from binary MJB file
|
||||||
|
// if vfs is not NULL, look up file in vfs before reading from disk
|
||||||
|
mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
|
||||||
|
if (vfs == NULL) {
|
||||||
|
return _mj_loadModel(filename, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = mj_registerVfsProvider(vfs);
|
||||||
|
if (index < 1) {
|
||||||
|
mju_error("mj_loadModel: could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjModel* model = _mj_loadModel(filename, index);
|
||||||
|
|
||||||
|
mjp_unregisterResourceProvider(index);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// de-allocate mjModel
|
// de-allocate mjModel
|
||||||
void mj_deleteModel(mjModel* m) {
|
void mj_deleteModel(mjModel* m) {
|
||||||
if (m) {
|
if (m) {
|
||||||
|
|||||||
+330
-55
@@ -28,6 +28,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -62,28 +63,33 @@ constexpr int kMaxAttributes = 255;
|
|||||||
|
|
||||||
constexpr int kCacheLine = 64;
|
constexpr int kCacheLine = 64;
|
||||||
|
|
||||||
|
// vfs prefix
|
||||||
|
constexpr const char* kVfsPrefix = mjVFS_PREFIX;
|
||||||
|
|
||||||
// A table of registered plugins, implemented as a linked list of array "blocks".
|
// A table of registered plugins, implemented as a linked list of array "blocks".
|
||||||
// This is a compromise that maintains a good degree of memory locality while not invalidating
|
// This is a compromise that maintains a good degree of memory locality while not invalidating
|
||||||
// existing pointers when growing the table. It is expected that for most users, the number of
|
// existing pointers when growing the table. It is expected that for most users, the number of
|
||||||
// plugins loaded into a program will be small enough to fit in the initial block, and so the global
|
// plugins loaded into a program will be small enough to fit in the initial block, and so the global
|
||||||
// table will behave like an array. Since pointers are never invalidated, we do not need to apply a
|
// table will behave like an array. Since pointers are never invalidated, we do not need to apply a
|
||||||
// read lock on the global table when resolving a plugin.
|
// read lock on the global table when resolving a plugin.
|
||||||
|
template<typename T>
|
||||||
struct alignas(kCacheLine) PluginTable {
|
struct alignas(kCacheLine) PluginTable {
|
||||||
static constexpr int kBlockSize = 15;
|
static constexpr int kBlockSize = 15;
|
||||||
|
|
||||||
PluginTable() {
|
PluginTable() {
|
||||||
for (int i = 0; i < kBlockSize; ++i) {
|
for (int i = 0; i < kBlockSize; ++i) {
|
||||||
mjp_defaultPlugin(&plugins[i]);
|
std::memset(&plugins[i], 0, sizeof(plugins[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mjpPlugin plugins[kBlockSize];
|
T plugins[kBlockSize];
|
||||||
PluginTable* next = nullptr;
|
PluginTable<T>* next = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(
|
static_assert(
|
||||||
sizeof(PluginTable) / kCacheLine ==
|
sizeof(PluginTable<mjpPlugin>) / kCacheLine ==
|
||||||
sizeof(PluginTable::plugins) / kCacheLine + (sizeof(PluginTable::plugins) % kCacheLine > 0),
|
sizeof(PluginTable<mjpPlugin>::plugins) / kCacheLine
|
||||||
|
+ (sizeof(PluginTable<mjpPlugin>::plugins) % kCacheLine > 0),
|
||||||
"PluginTable::next doesn't fit in the same cache line as the end of PluginTable::plugins");
|
"PluginTable::next doesn't fit in the same cache line as the end of PluginTable::plugins");
|
||||||
|
|
||||||
using Mutex = std::shared_mutex;
|
using Mutex = std::shared_mutex;
|
||||||
@@ -112,12 +118,13 @@ class ReentrantWriteLock {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
class Global {
|
class Global {
|
||||||
public:
|
public:
|
||||||
Global() {
|
Global() {
|
||||||
new(mutex_) Mutex;
|
new(mutex_) Mutex;
|
||||||
}
|
}
|
||||||
PluginTable& table() {
|
PluginTable<T>& table() {
|
||||||
return table_;
|
return table_;
|
||||||
}
|
}
|
||||||
std::atomic_int& count() {
|
std::atomic_int& count() {
|
||||||
@@ -132,7 +139,7 @@ class Global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PluginTable table_;
|
PluginTable<T> table_;
|
||||||
std::atomic_int count_;
|
std::atomic_int count_;
|
||||||
|
|
||||||
// A mutex whose destructor is never run.
|
// A mutex whose destructor is never run.
|
||||||
@@ -143,14 +150,60 @@ class Global {
|
|||||||
alignas(Mutex) unsigned char mutex_[sizeof(Mutex)];
|
alignas(Mutex) unsigned char mutex_[sizeof(Mutex)];
|
||||||
};
|
};
|
||||||
|
|
||||||
Global& GetGlobal() {
|
template<typename T>
|
||||||
static Global global;
|
Global<T>& GetGlobal() {
|
||||||
|
static Global<T> global;
|
||||||
static_assert(std::is_trivially_destructible_v<decltype(global)>);
|
static_assert(std::is_trivially_destructible_v<decltype(global)>);
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allocate new block for the global table
|
||||||
|
template<typename T>
|
||||||
|
PluginTable<T>* AddNewTableBlock(PluginTable<T>* table) {
|
||||||
|
char err[512];
|
||||||
|
err[0] = '\0';
|
||||||
|
|
||||||
|
#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
|
||||||
|
// aligned nothrow new is not available until macOS 10.14
|
||||||
|
posix_memalign(reinterpret_cast<void**>(&table->next),
|
||||||
|
alignof(PluginTable<T>), sizeof(PluginTable<T>));
|
||||||
|
if (table->next) new(table->next) PluginTable<T>;
|
||||||
|
#else
|
||||||
|
table->next = new(std::nothrow) PluginTable<T>;
|
||||||
|
#endif
|
||||||
|
if (!table->next) {
|
||||||
|
std::snprintf(err, sizeof(err), "failed to allocate memory for the global plugin table");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return table->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up a plugin by slot number
|
||||||
|
template<typename T>
|
||||||
|
const T* GetAtSlot(int slot, int nslot) {
|
||||||
|
if (slot < 0 || slot >= nslot) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Global<T>& global = GetGlobal<T>();
|
||||||
|
PluginTable<T>* table = &global.table();
|
||||||
|
|
||||||
|
// iterate over blocks in the global table until the local index is less than the block size
|
||||||
|
int local_idx = slot;
|
||||||
|
while (local_idx >= PluginTable<T>::kBlockSize) {
|
||||||
|
local_idx -= PluginTable<T>::kBlockSize;
|
||||||
|
table = table->next;
|
||||||
|
if (!table) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// local_idx is now a valid index into the current block
|
||||||
|
return &(table->plugins[local_idx]);
|
||||||
|
}
|
||||||
|
|
||||||
// return the length of a null-terminated string, or -1 if it is not terminated after kMaxNameLength
|
// return the length of a null-terminated string, or -1 if it is not terminated after kMaxNameLength
|
||||||
int strnlen(const char* s) {
|
int strklen(const char* s) {
|
||||||
for (int i = 0; i < kMaxNameLength; ++i) {
|
for (int i = 0; i < kMaxNameLength; ++i) {
|
||||||
if (!s[i]) {
|
if (!s[i]) {
|
||||||
return i;
|
return i;
|
||||||
@@ -161,7 +214,7 @@ int strnlen(const char* s) {
|
|||||||
|
|
||||||
// copy a null-terminated string into a new heap-allocated char array managed by a unique_ptr
|
// copy a null-terminated string into a new heap-allocated char array managed by a unique_ptr
|
||||||
std::unique_ptr<char[]> CopyName(const char* s) {
|
std::unique_ptr<char[]> CopyName(const char* s) {
|
||||||
int len = strnlen(s);
|
int len = strklen(s);
|
||||||
if (len == -1) {
|
if (len == -1) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -212,6 +265,15 @@ bool PluginsAreIdentical(const mjpPlugin& plugin1, const mjpPlugin& plugin2) {
|
|||||||
sizeof(mjpPlugin) - (ptr1 - reinterpret_cast<const char*>(&plugin1));
|
sizeof(mjpPlugin) - (ptr1 - reinterpret_cast<const char*>(&plugin1));
|
||||||
return !std::memcmp(ptr1, ptr2, remaining_size);
|
return !std::memcmp(ptr1, ptr2, remaining_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if two resource providers are identical
|
||||||
|
bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResourceProvider* p2) {
|
||||||
|
return (!std::strcmp(p1->prefix, p2->prefix) &&
|
||||||
|
p1->open == p2->open &&
|
||||||
|
p1->read == p2->read &&
|
||||||
|
p1->close == p2->close &&
|
||||||
|
p1->data == p2->data);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// globally register a plugin (thread-safe), return new slot id
|
// globally register a plugin (thread-safe), return new slot id
|
||||||
@@ -237,7 +299,7 @@ int mjp_registerPlugin(const mjpPlugin* plugin) {
|
|||||||
// check and copy the plugin name
|
// check and copy the plugin name
|
||||||
std::unique_ptr<char[]> name = CopyName(plugin->name);
|
std::unique_ptr<char[]> name = CopyName(plugin->name);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
if (strnlen(plugin->name) == -1) {
|
if (strklen(plugin->name) == -1) {
|
||||||
std::snprintf(err, sizeof(err),
|
std::snprintf(err, sizeof(err),
|
||||||
"plugin->name length exceeds the maximum limit of %d", kMaxNameLength);
|
"plugin->name length exceeds the maximum limit of %d", kMaxNameLength);
|
||||||
} else {
|
} else {
|
||||||
@@ -253,7 +315,7 @@ int mjp_registerPlugin(const mjpPlugin* plugin) {
|
|||||||
for (int i = 0; i < plugin->nattribute; ++i) {
|
for (int i = 0; i < plugin->nattribute; ++i) {
|
||||||
std::unique_ptr<char[]> attr = CopyName(plugin->attributes[i]);
|
std::unique_ptr<char[]> attr = CopyName(plugin->attributes[i]);
|
||||||
if (!attr) {
|
if (!attr) {
|
||||||
if (strnlen(plugin->attributes[i]) == -1) {
|
if (strklen(plugin->attributes[i]) == -1) {
|
||||||
std::snprintf(
|
std::snprintf(
|
||||||
err, sizeof(err),
|
err, sizeof(err),
|
||||||
"plugin->attributes[%d] exceeds the maximum limit of %d", i, kMaxAttributes);
|
"plugin->attributes[%d] exceeds the maximum limit of %d", i, kMaxAttributes);
|
||||||
@@ -266,16 +328,16 @@ int mjp_registerPlugin(const mjpPlugin* plugin) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Global& global = GetGlobal();
|
Global<mjpPlugin>& global = GetGlobal<mjpPlugin>();
|
||||||
auto lock = global.lock_mutex_exclusively();
|
auto lock = global.lock_mutex_exclusively();
|
||||||
|
|
||||||
int count = global.count().load(std::memory_order_acquire);
|
int count = global.count().load(std::memory_order_acquire);
|
||||||
int local_idx = 0;
|
int local_idx = 0;
|
||||||
PluginTable* table = &global.table();
|
PluginTable<mjpPlugin>* table = &global.table();
|
||||||
|
|
||||||
// check if a non-identical plugin with the same name has already been registered
|
// check if a non-identical plugin with the same name has already been registered
|
||||||
for (int i = 0; i < count; ++i, ++local_idx) {
|
for (int i = 0; i < count; ++i, ++local_idx) {
|
||||||
if (local_idx == PluginTable::kBlockSize) {
|
if (local_idx == PluginTable<mjpPlugin>::kBlockSize) {
|
||||||
local_idx = 0;
|
local_idx = 0;
|
||||||
table = table->next;
|
table = table->next;
|
||||||
}
|
}
|
||||||
@@ -291,21 +353,12 @@ int mjp_registerPlugin(const mjpPlugin* plugin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allocate a new block of PluginTable if the last allocated block is full
|
// allocate a new block of PluginTable if the last allocated block is full
|
||||||
if (local_idx == PluginTable::kBlockSize) {
|
if (local_idx == PluginTable<mjpPlugin>::kBlockSize) {
|
||||||
local_idx = 0;
|
local_idx = 0;
|
||||||
#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
|
table = AddNewTableBlock<mjpPlugin>(table);
|
||||||
// aligned nothrow new is not available until macOS 10.14
|
if (!table) {
|
||||||
posix_memalign(reinterpret_cast<void**>(&table->next),
|
|
||||||
alignof(PluginTable), sizeof(PluginTable));
|
|
||||||
if (table->next) new(table->next) PluginTable;
|
|
||||||
#else
|
|
||||||
table->next = new(std::nothrow) PluginTable;
|
|
||||||
#endif
|
|
||||||
if (!table->next) {
|
|
||||||
std::snprintf(err, sizeof(err), "failed to allocate memory for the global plugin table");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
table = table->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// release the attribute names from unique_ptr into a plain array
|
// release the attribute names from unique_ptr into a plain array
|
||||||
@@ -347,29 +400,11 @@ int mjp_registerPlugin(const mjpPlugin* plugin) {
|
|||||||
|
|
||||||
// look up plugin by slot number, assuming that mjp_pluginCount has already been called
|
// look up plugin by slot number, assuming that mjp_pluginCount has already been called
|
||||||
const mjpPlugin* mjp_getPluginAtSlotUnsafe(int slot, int nslot) {
|
const mjpPlugin* mjp_getPluginAtSlotUnsafe(int slot, int nslot) {
|
||||||
if (slot < 0 || slot >= nslot) {
|
const mjpPlugin* plugin = GetAtSlot<mjpPlugin>(slot, nslot);
|
||||||
|
if (!plugin || !plugin->name) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
return plugin;
|
||||||
Global& global = GetGlobal();
|
|
||||||
PluginTable* table = &global.table();
|
|
||||||
|
|
||||||
// iterate over blocks in the global table until the local index is less the block size
|
|
||||||
int local_idx = slot;
|
|
||||||
while (local_idx >= PluginTable::kBlockSize) {
|
|
||||||
local_idx -= PluginTable::kBlockSize;
|
|
||||||
table = table->next;
|
|
||||||
if (!table) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// local_idx is now a valid index into the current block
|
|
||||||
const mjpPlugin& plugin = table->plugins[local_idx];
|
|
||||||
if (!plugin.name) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return &plugin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// look up plugin by name, assuming that mjp_pluginCount has already been called
|
// look up plugin by name, assuming that mjp_pluginCount has already been called
|
||||||
@@ -380,12 +415,12 @@ const mjpPlugin* mjp_getPluginUnsafe(const char* name, int* slot, int nslot) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Global& plugins = GetGlobal();
|
Global<mjpPlugin>& plugins = GetGlobal<mjpPlugin>();
|
||||||
PluginTable* table = &plugins.table();
|
PluginTable<mjpPlugin>* table = &plugins.table();
|
||||||
int found_slot = 0;
|
int found_slot = 0;
|
||||||
while (table) {
|
while (table) {
|
||||||
for (int i = 0;
|
for (int i = 0;
|
||||||
i < PluginTable::kBlockSize && found_slot < nslot;
|
i < PluginTable<mjpPlugin>::kBlockSize && found_slot < nslot;
|
||||||
++i, ++found_slot) {
|
++i, ++found_slot) {
|
||||||
const mjpPlugin& plugin = table->plugins[i];
|
const mjpPlugin& plugin = table->plugins[i];
|
||||||
|
|
||||||
@@ -407,10 +442,9 @@ const mjpPlugin* mjp_getPluginUnsafe(const char* name, int* slot, int nslot) {
|
|||||||
|
|
||||||
// return the number of globally registered plugins
|
// return the number of globally registered plugins
|
||||||
int mjp_pluginCount() {
|
int mjp_pluginCount() {
|
||||||
return GetGlobal().count().load(std::memory_order_acquire);
|
return GetGlobal<mjpPlugin>().count().load(std::memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// look up a plugin by slot number
|
// look up a plugin by slot number
|
||||||
const mjpPlugin* mjp_getPluginAtSlot(int slot) {
|
const mjpPlugin* mjp_getPluginAtSlot(int slot) {
|
||||||
const int count = mjp_pluginCount();
|
const int count = mjp_pluginCount();
|
||||||
@@ -466,6 +500,247 @@ const char* mj_getPluginConfig(const mjModel* m, int plugin_id, const char* attr
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set default resource provider definition
|
||||||
|
void mjp_defaultResourceProvider(mjpResourceProvider* provider) {
|
||||||
|
std::memset(provider, 0, sizeof(*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)) {
|
||||||
|
mju_warning("provider->prefix is '%s' which is reserved", provider->prefix);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mjp_registerResourceProviderInternal(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");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider->open || !provider->read || !provider->close) {
|
||||||
|
mju_warning("provider must have the open, read, and close callbacks defined");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char err[512];
|
||||||
|
err[0] = '\0';
|
||||||
|
|
||||||
|
// ========= ATTENTION! ==========================================================================
|
||||||
|
// Do not handle objects with nontrivial destructors outside of this lambda.
|
||||||
|
// Do not call mju_error inside this lambda.
|
||||||
|
int slot = [&]() -> int {
|
||||||
|
bool vfs_provider = false;
|
||||||
|
std::unique_ptr<char[]> prefix;
|
||||||
|
|
||||||
|
// check if this is a VFS provider
|
||||||
|
if (!std::strcmp(mjVFS_PREFIX, provider->prefix)) {
|
||||||
|
vfs_provider = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy prefix
|
||||||
|
if (!vfs_provider) {
|
||||||
|
prefix = CopyName(provider->prefix);
|
||||||
|
if (!prefix) {
|
||||||
|
if (strklen(provider->prefix) == -1) {
|
||||||
|
std::snprintf(err, sizeof(err),
|
||||||
|
"provider->prefix length exceeds the maximum limit of %d", kMaxNameLength);
|
||||||
|
} else {
|
||||||
|
std::snprintf(err, sizeof(err), "failed to allocate memory for resource provider prefix");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Global<mjpResourceProvider>& global = GetGlobal<mjpResourceProvider>();
|
||||||
|
auto lock = global.lock_mutex_exclusively();
|
||||||
|
int count = global.count().load(std::memory_order_acquire);
|
||||||
|
int local_idx = 0;
|
||||||
|
int free_idx = -1, free_local_idx = -1;
|
||||||
|
PluginTable<mjpResourceProvider>* table = &global.table();
|
||||||
|
PluginTable<mjpResourceProvider>* free_table = nullptr;
|
||||||
|
|
||||||
|
// check if a non-identical provider with the same name has already been registered
|
||||||
|
for (int i = 0; i < count; ++i, ++local_idx) {
|
||||||
|
if (local_idx == PluginTable<mjpResourceProvider>::kBlockSize) {
|
||||||
|
local_idx = 0;
|
||||||
|
table = table->next;
|
||||||
|
}
|
||||||
|
mjpResourceProvider& existing = table->plugins[local_idx];
|
||||||
|
|
||||||
|
// VFS providers can safely go in open slots
|
||||||
|
if (vfs_provider && existing.prefix == nullptr && free_idx == -1) {
|
||||||
|
free_table = table;
|
||||||
|
free_idx = i;
|
||||||
|
free_local_idx = local_idx;
|
||||||
|
|
||||||
|
// can skip the rest
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vfs_provider) {
|
||||||
|
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 (ResourceProvidersAreIdentical(provider, &existing)) {
|
||||||
|
return i;
|
||||||
|
} else {
|
||||||
|
std::snprintf(err, sizeof(err),
|
||||||
|
"a resource provider with prefix '%s' cannot be registered",
|
||||||
|
provider->prefix);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate a new block of PluginTable if the last allocated block is full
|
||||||
|
if (free_local_idx == -1 && local_idx == PluginTable<mjpResourceProvider>::kBlockSize) {
|
||||||
|
local_idx = 0;
|
||||||
|
table = AddNewTableBlock<mjpResourceProvider>(table);
|
||||||
|
if (!table) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all checks passed, register the plugin into the global table
|
||||||
|
mjpResourceProvider& registered_provider = table->plugins[local_idx];
|
||||||
|
if (free_local_idx != -1) {
|
||||||
|
registered_provider = free_table->plugins[free_local_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
registered_provider = *provider;
|
||||||
|
registered_provider.prefix = (!vfs_provider) ? prefix.release() : kVfsPrefix;
|
||||||
|
|
||||||
|
// increment the global plugin count with a release memory barrier
|
||||||
|
if (free_idx == -1) {
|
||||||
|
free_idx = count;
|
||||||
|
global.count().store(count + 1, std::memory_order_release);
|
||||||
|
}
|
||||||
|
return free_idx;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// ========= ATTENTION! ==========================================================================
|
||||||
|
// End of safe lambda, do not handle objects with non-trivial destructors beyond this point.
|
||||||
|
|
||||||
|
// plugin registration failed, throw a warning
|
||||||
|
if (slot < 0) {
|
||||||
|
err[sizeof(err) - 1] = '\0';
|
||||||
|
mju_warning("%s", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slot+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// globally unregister resource provider (thread-safe)
|
||||||
|
// only used for VFS resource providers
|
||||||
|
void mjp_unregisterResourceProvider(int slot) {
|
||||||
|
// shift slot to zero-index
|
||||||
|
slot--;
|
||||||
|
|
||||||
|
if (slot < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get global table, acquire lock
|
||||||
|
Global<mjpResourceProvider>& global = GetGlobal<mjpResourceProvider>();
|
||||||
|
auto lock = global.lock_mutex_exclusively();
|
||||||
|
int count = global.count().load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (slot >= count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginTable<mjpResourceProvider>* table = &global.table();
|
||||||
|
|
||||||
|
// iterate over blocks in the global table until the local index is less than the block size
|
||||||
|
int local_idx = slot;
|
||||||
|
while (local_idx >= PluginTable<mjpResourceProvider>::kBlockSize) {
|
||||||
|
local_idx -= PluginTable<mjpResourceProvider>::kBlockSize;
|
||||||
|
table = table->next;
|
||||||
|
if (!table) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// local_idx is now a valid index into the current block
|
||||||
|
mjpResourceProvider& provider = table->plugins[local_idx];
|
||||||
|
|
||||||
|
// no-op for anything other than VFS resource providers
|
||||||
|
if (provider.prefix == kVfsPrefix) {
|
||||||
|
provider.prefix = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the number of globally registered resource providers
|
||||||
|
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
|
||||||
|
const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
|
||||||
|
const int count = mjp_resourceProviderCount();
|
||||||
|
if (!resource_name || !resource_name[0]) {
|
||||||
|
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))) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Global<mjpResourceProvider>& global = GetGlobal<mjpResourceProvider>();
|
||||||
|
PluginTable<mjpResourceProvider>* table = &global.table();
|
||||||
|
int found_slot = 0;
|
||||||
|
|
||||||
|
while (table) {
|
||||||
|
for (int i = 0;
|
||||||
|
i < PluginTable<mjpPlugin>::kBlockSize && found_slot < count;
|
||||||
|
++i, ++found_slot) {
|
||||||
|
|
||||||
|
const mjpResourceProvider& provider = table->plugins[i];
|
||||||
|
const char *prefix = provider.prefix;
|
||||||
|
|
||||||
|
if (prefix != nullptr &&
|
||||||
|
!std::strncmp(prefix, resource_name, std::strlen(prefix))) {
|
||||||
|
return &provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table = table->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up a resource provider by slot number
|
||||||
|
const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot) {
|
||||||
|
// mjp_resourceProviderCount uses memory_order_acquire which acts as a barrier
|
||||||
|
// that guarantees that all providers up to `count` have been completely inserted
|
||||||
|
const int count = mjp_resourceProviderCount();
|
||||||
|
|
||||||
|
// shift slot to be zero-indexed
|
||||||
|
const mjpResourceProvider* provider = GetAtSlot<mjpResourceProvider>(slot - 1, count);
|
||||||
|
if (!provider || provider->prefix[0] == '\0') {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
// load plugins from a dynamic library
|
// load plugins from a dynamic library
|
||||||
void mj_loadPluginLibrary(const char* path) {
|
void mj_loadPluginLibrary(const char* path) {
|
||||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||||
@@ -483,7 +758,7 @@ void mj_loadAllPluginLibraries(const char* directory,
|
|||||||
int nplugin_before;
|
int nplugin_before;
|
||||||
int nplugin_after;
|
int nplugin_after;
|
||||||
|
|
||||||
Global& global = GetGlobal();
|
Global<mjpPlugin>& global = GetGlobal<mjpPlugin>();
|
||||||
{
|
{
|
||||||
auto lock = global.lock_mutex_exclusively();
|
auto lock = global.lock_mutex_exclusively();
|
||||||
nplugin_before = mjp_pluginCount();
|
nplugin_before = mjp_pluginCount();
|
||||||
|
|||||||
@@ -28,15 +28,33 @@ MJAPI void mjp_defaultPlugin(mjpPlugin* plugin);
|
|||||||
// globally register a plugin (thread-safe), return new slot id
|
// globally register a plugin (thread-safe), return new slot id
|
||||||
MJAPI int mjp_registerPlugin(const mjpPlugin* plugin);
|
MJAPI int mjp_registerPlugin(const mjpPlugin* plugin);
|
||||||
|
|
||||||
|
// globally register a resource provider (thread-safe), return new slot id
|
||||||
|
MJAPI int mjp_registerResourceProvider(const mjpResourceProvider* provider);
|
||||||
|
|
||||||
|
// globally unregister a resource provider (thread-safe)
|
||||||
|
MJAPI void mjp_unregisterResourceProvider(int slot);
|
||||||
|
|
||||||
// return the number of globally registered plugins
|
// return the number of globally registered plugins
|
||||||
MJAPI int mjp_pluginCount();
|
MJAPI int mjp_pluginCount();
|
||||||
|
|
||||||
|
// return the number of globally registered resource providers
|
||||||
|
MJAPI int mjp_resourceProviderCount();
|
||||||
|
|
||||||
// look up a plugin by name, optionally also get its registered slot number
|
// look up a plugin by name, optionally also get its registered slot number
|
||||||
MJAPI const mjpPlugin* mjp_getPlugin(const char* name, int* slot);
|
MJAPI const mjpPlugin* mjp_getPlugin(const char* name, int* slot);
|
||||||
|
|
||||||
|
// set default resource provider definition
|
||||||
|
MJAPI void mjp_defaultResourceProvider(mjpResourceProvider* provider);
|
||||||
|
|
||||||
|
// look up a resource provider that matches its prefix against the given resource name
|
||||||
|
MJAPI const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name);
|
||||||
|
|
||||||
// look up a plugin by slot number
|
// look up a plugin by slot number
|
||||||
MJAPI const mjpPlugin* mjp_getPluginAtSlot(int slot);
|
MJAPI const mjpPlugin* mjp_getPluginAtSlot(int slot);
|
||||||
|
|
||||||
|
// look up a resource provider by slot number
|
||||||
|
MJAPI const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot);
|
||||||
|
|
||||||
// return a config attribute of a plugin instance
|
// return a config attribute of a plugin instance
|
||||||
// NULL: invalid plugin instance ID or attribute name
|
// NULL: invalid plugin instance ID or attribute name
|
||||||
MJAPI const char* mj_getPluginConfig(const mjModel* m, int plugin_id, const char* attrib);
|
MJAPI const char* mj_getPluginConfig(const mjModel* m, int plugin_id, const char* attrib);
|
||||||
@@ -51,10 +69,13 @@ MJAPI void mj_loadAllPluginLibraries(const char* directory, mjfPluginLibraryLoad
|
|||||||
// MuJoCo-internal functions beyond this point.
|
// MuJoCo-internal functions beyond this point.
|
||||||
// "Unsafe" suffix indicates that improper use of these functions may result in data races.
|
// "Unsafe" suffix indicates that improper use of these functions may result in data races.
|
||||||
//
|
//
|
||||||
// The unsafe functions assume that called mjp_pluginCount has already been called, and that it is
|
// The unsafe functions assume that mjp_pluginCount has already been called, and that all plugins
|
||||||
// safe to assume that all plugins up to `count` have been completely written into the global table.
|
// up to `count` have been completely written into the global table.
|
||||||
// =================================================================================================
|
// =================================================================================================
|
||||||
|
|
||||||
|
// internal version of mjp_registerResourceProvider without prechecks on reserved prefixes
|
||||||
|
MJAPI int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider);
|
||||||
|
|
||||||
// look up a plugin by name, assuming that mjp_pluginCount has already been called
|
// look up a plugin by name, assuming that mjp_pluginCount has already been called
|
||||||
const mjpPlugin* mjp_getPluginUnsafe(const char* name, int* slot, int nslot);
|
const mjpPlugin* mjp_getPluginUnsafe(const char* name, int* slot, int nslot);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
// Copyright 2022 DeepMind Technologies Limited
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "engine/engine_resource.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "engine/engine_plugin.h"
|
||||||
|
#include "engine/engine_util_errmem.h"
|
||||||
|
|
||||||
|
// file buffer used internally for the OS filesystem
|
||||||
|
typedef struct {
|
||||||
|
void* buffer;
|
||||||
|
int nbuffer;
|
||||||
|
} file_buffer;
|
||||||
|
|
||||||
|
|
||||||
|
// open the given resource; if the name doesn't have a prefix matching with a
|
||||||
|
// resource provider, then the default_provider is used
|
||||||
|
// if default_provider non-positive, then the OS filesystem is used
|
||||||
|
mjResource* mju_openResource(const char* name, int default_provider) {
|
||||||
|
mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource));
|
||||||
|
const mjpResourceProvider* provider = NULL;
|
||||||
|
if (resource == NULL) {
|
||||||
|
mju_error("mju_openResource: could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy name
|
||||||
|
resource->name = mju_malloc(sizeof(char) * (strlen(name) + 1));
|
||||||
|
if (resource->name == NULL) {
|
||||||
|
mju_free(resource);
|
||||||
|
mju_error("mju_openResource: could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
strcpy(resource->name, name);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
if (provider->open(resource)) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
mju_warning("mju_openResource: could not open resource '%s", name);
|
||||||
|
mju_free(resource->name);
|
||||||
|
mju_free(resource);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to default provider
|
||||||
|
if (default_provider > 0) {
|
||||||
|
provider = mjp_getResourceProviderAtSlot(default_provider);
|
||||||
|
if (provider == NULL) {
|
||||||
|
mju_warning("mju_openResource: unknown resource provider");
|
||||||
|
mju_free(resource->name);
|
||||||
|
mju_free(resource);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
resource->read = provider->read;
|
||||||
|
resource->close = provider->close;
|
||||||
|
resource->provider_data = provider->data;
|
||||||
|
if (provider->open(resource)) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
mju_warning("mju_openResource: could not open resource '%s", name);
|
||||||
|
mju_free(resource->name);
|
||||||
|
mju_free(resource);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastly fallback to OS filesystem
|
||||||
|
else {
|
||||||
|
resource->read = NULL;
|
||||||
|
resource->close = NULL;
|
||||||
|
resource->provider_data = NULL;
|
||||||
|
resource->data = mju_malloc(sizeof(file_buffer));
|
||||||
|
file_buffer* fb = (file_buffer*) resource->data;
|
||||||
|
fb->buffer = mju_fileToMemory(name, &(fb->nbuffer));
|
||||||
|
if (fb->buffer == NULL) {
|
||||||
|
mju_warning("mju_openResource: unknown file '%s'", name);
|
||||||
|
mju_free(fb);
|
||||||
|
mju_free(resource->name);
|
||||||
|
mju_free(resource);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// close the given resource; no-op if resource is NULL
|
||||||
|
void mju_closeResource(mjResource* resource) {
|
||||||
|
if (resource == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the resource provider to close resource
|
||||||
|
if (resource->close) {
|
||||||
|
resource->close(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if provider is NULL, then OS filesystem is used
|
||||||
|
else {
|
||||||
|
file_buffer* fb = (file_buffer*) resource->data;
|
||||||
|
mju_free(fb->buffer);
|
||||||
|
mju_free(fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// free name and resource
|
||||||
|
mju_free(resource->name);
|
||||||
|
mju_free(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// set buffer to bytes read from the resource and return number of bytes in buffer;
|
||||||
|
// return negative value if error
|
||||||
|
int mju_readResource(mjResource* resource, const void** buffer) {
|
||||||
|
if (resource == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource->read) {
|
||||||
|
return resource->read(resource, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if provider is NULL, then OS filesystem is used
|
||||||
|
const file_buffer* fb = (file_buffer*) resource->data;
|
||||||
|
*buffer = fb->buffer;
|
||||||
|
return fb->nbuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// read file into memory buffer (allocated here with mju_malloc)
|
||||||
|
void* mju_fileToMemory(const char* filename, int* filesize) {
|
||||||
|
// open file
|
||||||
|
*filesize = 0;
|
||||||
|
FILE* fp = fopen(filename, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find size
|
||||||
|
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("Failed to calculate size for '%s'", filename);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure file size fits in int
|
||||||
|
long long_filesize = ftell(fp); // NOLINT(runtime/int)
|
||||||
|
if (long_filesize > INT_MAX) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("File size over 2GB is not supported. File: '%s'", filename);
|
||||||
|
return NULL;
|
||||||
|
} else if (long_filesize < 0) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("Failed to calculate size for '%s'", filename);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*filesize = long_filesize;
|
||||||
|
|
||||||
|
// go back to start of file
|
||||||
|
if (fseek(fp, 0, SEEK_SET) != 0) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("Read error while reading '%s'", filename);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate and read
|
||||||
|
void* buffer = mju_malloc(*filesize);
|
||||||
|
if (!buffer) {
|
||||||
|
mju_error("mjFileToMemory: could not allocate memory");
|
||||||
|
}
|
||||||
|
size_t bytes_read = fread(buffer, 1, *filesize, fp);
|
||||||
|
|
||||||
|
// check that read data matches file size
|
||||||
|
if (bytes_read != *filesize) { // SHOULD NOT OCCUR
|
||||||
|
if (ferror(fp)) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_free(buffer);
|
||||||
|
*filesize = 0;
|
||||||
|
mju_warning("Read error while reading '%s'", filename);
|
||||||
|
return NULL;
|
||||||
|
} else if (feof(fp)) {
|
||||||
|
*filesize = bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close file, return contents
|
||||||
|
fclose(fp);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
@@ -12,13 +12,28 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#ifndef MUJOCO_SRC_ENGINE_ENGINE_FILE_H_
|
#ifndef MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
|
||||||
#define MUJOCO_SRC_ENGINE_ENGINE_FILE_H_
|
#define MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
|
||||||
|
|
||||||
|
#include <mujoco/mjexport.h>
|
||||||
|
#include "engine/engine_plugin.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// open the given resource; if the name doesn't have a prefix matching with a
|
||||||
|
// resource provider, then the default_provider is used
|
||||||
|
// if default_provider non-positive, then the OS filesystem is used
|
||||||
|
MJAPI mjResource* mju_openResource(const char* name, int default_provider);
|
||||||
|
|
||||||
|
// close the given resource; no-op if resource is NULL
|
||||||
|
MJAPI void mju_closeResource(mjResource* resource);
|
||||||
|
|
||||||
|
// set buffer to bytes read from the resource and return number of bytes in buffer;
|
||||||
|
// return negative value if error
|
||||||
|
MJAPI int mju_readResource(mjResource* resource, const void** buffer);
|
||||||
|
|
||||||
// read file into memory buffer (allocated here with mju_malloc)
|
// read file into memory buffer (allocated here with mju_malloc)
|
||||||
void* mju_fileToMemory(const char* filename, int* filesize);
|
void* mju_fileToMemory(const char* filename, int* filesize);
|
||||||
|
|
||||||
@@ -26,4 +41,4 @@ void* mju_fileToMemory(const char* filename, int* filesize);
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_ENGINE_ENGINE_FILE_H_
|
#endif // MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
|
||||||
+55
-2
@@ -18,7 +18,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "engine/engine_array_safety.h"
|
#include "engine/engine_array_safety.h"
|
||||||
#include "engine/engine_file.h"
|
#include "engine/engine_plugin.h"
|
||||||
|
#include "engine/engine_resource.h"
|
||||||
#include "engine/engine_util_errmem.h"
|
#include "engine/engine_util_errmem.h"
|
||||||
#include "engine/engine_util_misc.h"
|
#include "engine/engine_util_misc.h"
|
||||||
|
|
||||||
@@ -154,7 +155,6 @@ int mj_findFileVFS(const mjVFS* vfs, const char* filename) {
|
|||||||
// strip path
|
// strip path
|
||||||
char newname[mjMAXVFSNAME];
|
char newname[mjMAXVFSNAME];
|
||||||
vfs_strippath(newname, filename);
|
vfs_strippath(newname, filename);
|
||||||
|
|
||||||
// find specific file
|
// find specific file
|
||||||
for (int i=0; i<vfs->nfile; i++) {
|
for (int i=0; i<vfs->nfile; i++) {
|
||||||
if (strncmp(newname, vfs->filename[i], mjMAXVFSNAME)==0) {
|
if (strncmp(newname, vfs->filename[i], mjMAXVFSNAME)==0) {
|
||||||
@@ -210,3 +210,56 @@ void mj_deleteVFS(mjVFS* vfs) {
|
|||||||
|
|
||||||
memset(vfs, 0, sizeof(mjVFS));
|
memset(vfs, 0, sizeof(mjVFS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// open callback for the VFS resource provider
|
||||||
|
static int vfs_open_callback(mjResource* resource) {
|
||||||
|
if (!resource || !resource->provider_data || !resource->name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mjVFS* vfs = (const mjVFS*) resource->provider_data;
|
||||||
|
return mj_findFileVFS(vfs, resource->name) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// read callback for the VFS resource provider
|
||||||
|
static int vfs_read_callback(mjResource* resource, const void** buffer) {
|
||||||
|
if (!resource || !resource->provider_data) {
|
||||||
|
*buffer = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mjVFS* vfs = (const mjVFS*) resource->provider_data;
|
||||||
|
int i = mj_findFileVFS(vfs, resource->name);
|
||||||
|
if (i < 0) {
|
||||||
|
*buffer = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*buffer = vfs->filedata[i];
|
||||||
|
return vfs->filesize[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// close callback for the VFS resource provider
|
||||||
|
static void vfs_close_callback(mjResource* resource) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// registers a VFS resource provider; returns the index of the provider
|
||||||
|
int mj_registerVfsProvider(const mjVFS* vfs) {
|
||||||
|
mjpResourceProvider provider = {
|
||||||
|
.prefix = mjVFS_PREFIX,
|
||||||
|
.open = &vfs_open_callback,
|
||||||
|
.read = &vfs_read_callback,
|
||||||
|
.close = &vfs_close_callback,
|
||||||
|
.data = (void*) vfs
|
||||||
|
};
|
||||||
|
|
||||||
|
return mjp_registerResourceProviderInternal(&provider);
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ MJAPI int mj_deleteFileVFS(mjVFS* vfs, const char* filename);
|
|||||||
// delete all files from VFS
|
// delete all files from VFS
|
||||||
MJAPI void mj_deleteVFS(mjVFS* vfs);
|
MJAPI void mj_deleteVFS(mjVFS* vfs);
|
||||||
|
|
||||||
|
// registers a VFS resource provider; returns the index of the provider
|
||||||
|
MJAPI int mj_registerVfsProvider(const mjVFS* vfs);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+81
-147
@@ -30,13 +30,12 @@
|
|||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include "cc/array_safety.h"
|
#include "cc/array_safety.h"
|
||||||
#include "engine/engine_crossplatform.h"
|
#include "engine/engine_crossplatform.h"
|
||||||
#include "engine/engine_file.h"
|
#include "engine/engine_resource.h"
|
||||||
#include "engine/engine_macro.h"
|
#include "engine/engine_macro.h"
|
||||||
#include "engine/engine_util_blas.h"
|
#include "engine/engine_util_blas.h"
|
||||||
#include "engine/engine_util_errmem.h"
|
#include "engine/engine_util_errmem.h"
|
||||||
#include "engine/engine_util_solve.h"
|
#include "engine/engine_util_solve.h"
|
||||||
#include "engine/engine_util_spatial.h"
|
#include "engine/engine_util_spatial.h"
|
||||||
#include "engine/engine_vfs.h"
|
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "user/user_objects.h"
|
#include "user/user_objects.h"
|
||||||
#include "user/user_util.h"
|
#include "user/user_util.h"
|
||||||
@@ -180,7 +179,7 @@ template <typename T> static T* VecToArray(std::vector<T>& vector, bool clear =
|
|||||||
|
|
||||||
|
|
||||||
// compiler
|
// compiler
|
||||||
void mjCMesh::Compile(const mjVFS* vfs) {
|
void mjCMesh::Compile(int default_provider) {
|
||||||
// load file
|
// load file
|
||||||
if (!file.empty()) {
|
if (!file.empty()) {
|
||||||
// remove path from file if necessary
|
// remove path from file if necessary
|
||||||
@@ -191,11 +190,11 @@ void mjCMesh::Compile(const mjVFS* vfs) {
|
|||||||
// load STL, OBJ or MSH
|
// load STL, OBJ or MSH
|
||||||
string ext = mjuu_getext(file);
|
string ext = mjuu_getext(file);
|
||||||
if (!strcasecmp(ext.c_str(), ".stl")) {
|
if (!strcasecmp(ext.c_str(), ".stl")) {
|
||||||
LoadSTL(vfs);
|
LoadSTL(default_provider);
|
||||||
} else if (!strcasecmp(ext.c_str(), ".obj")) {
|
} else if (!strcasecmp(ext.c_str(), ".obj")) {
|
||||||
LoadOBJ(vfs);
|
LoadOBJ(default_provider);
|
||||||
} else if (!strcasecmp(ext.c_str(), ".msh")) {
|
} else if (!strcasecmp(ext.c_str(), ".msh")) {
|
||||||
LoadMSH(vfs);
|
LoadMSH(default_provider);
|
||||||
} else {
|
} else {
|
||||||
throw mjCError(this, "Unknown mesh file type: %s", file.c_str());
|
throw mjCError(this, "Unknown mesh file type: %s", file.c_str());
|
||||||
}
|
}
|
||||||
@@ -584,34 +583,36 @@ void mjCMesh::RemoveRepeated() {
|
|||||||
|
|
||||||
|
|
||||||
// load OBJ mesh
|
// load OBJ mesh
|
||||||
void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
void mjCMesh::LoadOBJ(int default_provider) {
|
||||||
|
|
||||||
// make filename
|
// make filename
|
||||||
string filename = mjuu_makefullname(
|
string filename = mjuu_makefullname(
|
||||||
model->modelfiledir, model->meshdir, file);
|
model->modelfiledir, model->meshdir, file);
|
||||||
|
mjResource* r = nullptr;
|
||||||
|
|
||||||
tinyobj::ObjReader objReader;
|
// try reading from default provider
|
||||||
char* buffer = nullptr;
|
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
if (vfs) {
|
// try reading from filesystem
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
if (default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
if (id >= 0) {
|
throw mjCError(this, "could not parse OBJ file '%s'", filename.c_str());
|
||||||
buffer = static_cast<char*>(vfs->filedata[id]);
|
|
||||||
int buffer_sz = vfs->filesize[id];
|
|
||||||
// TODO(etom): support .mtl files in the VFS case?
|
|
||||||
objReader.ParseFromString(std::string(buffer, buffer_sz), std::string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not found in vfs, read from file
|
tinyobj::ObjReader objReader;
|
||||||
if (!buffer) {
|
const void* bytes = nullptr;
|
||||||
objReader.ParseFromFile(filename);
|
int buffer_sz = mju_readResource(r, &bytes);
|
||||||
|
if (buffer_sz < 0) {
|
||||||
|
mju_closeResource(r);
|
||||||
|
throw mjCError(this, "could not parse OBJ file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(etom): support .mtl files?
|
||||||
|
const char* buffer = (const char*) bytes;
|
||||||
|
objReader.ParseFromString(std::string(buffer, buffer_sz), std::string());
|
||||||
|
mju_closeResource(r);
|
||||||
|
|
||||||
if (!objReader.Valid()) {
|
if (!objReader.Valid()) {
|
||||||
std::stringstream msg;
|
throw mjCError(this, "could not parse OBJ file '%s'", filename.c_str());
|
||||||
msg << "could not parse OBJ file '" << filename << "': \n"
|
|
||||||
<< objReader.Error();
|
|
||||||
throw mjCError(this, "%s", msg.str().c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& attrib = objReader.GetAttrib();
|
const auto& attrib = objReader.GetAttrib();
|
||||||
@@ -685,56 +686,42 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
|
|
||||||
|
|
||||||
// load STL binary mesh
|
// load STL binary mesh
|
||||||
void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
void mjCMesh::LoadSTL(int default_provider) {
|
||||||
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
||||||
|
|
||||||
// make filename
|
// make filename
|
||||||
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
||||||
|
mjResource* r = nullptr;
|
||||||
|
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
|
if(!default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
|
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// get file data in buffer
|
// get file data in buffer
|
||||||
char* buffer = 0;
|
char* buffer = 0;
|
||||||
int buffer_sz = 0;
|
int buffer_sz = mju_readResource(r, (const void**) &buffer);
|
||||||
bool own_buffer = false;
|
|
||||||
if (vfs) {
|
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
|
||||||
if (id>=0) {
|
|
||||||
buffer = (char*)vfs->filedata[id];
|
|
||||||
buffer_sz = vfs->filesize[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not found in vfs, read from file
|
|
||||||
if (!buffer) {
|
|
||||||
buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz);
|
|
||||||
own_buffer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// still not found
|
// still not found
|
||||||
if (!buffer) {
|
if (buffer_sz < 0) {
|
||||||
|
mju_closeResource(r);
|
||||||
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||||
} else if (!buffer_sz) {
|
} else if (!buffer_sz) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "STL file '%s' is empty", filename.c_str());
|
throw mjCError(this, "STL file '%s' is empty", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure there is enough data for header
|
// make sure there is enough data for header
|
||||||
if (buffer_sz<84) {
|
if (buffer_sz<84) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "invalid header in STL file '%s'", filename.c_str());
|
throw mjCError(this, "invalid header in STL file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// get number of triangles, check bounds
|
// get number of triangles, check bounds
|
||||||
nface = *(unsigned int*)(buffer+80);
|
nface = *(unsigned int*)(buffer+80);
|
||||||
if (nface<1 || nface>200000) {
|
if (nface<1 || nface>200000) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this,
|
throw mjCError(this,
|
||||||
"number of faces should be between 1 and 200000 in STL file '%s';"
|
"number of faces should be between 1 and 200000 in STL file '%s';"
|
||||||
" perhaps this is an ASCII file?", filename.c_str());
|
" perhaps this is an ASCII file?", filename.c_str());
|
||||||
@@ -742,10 +729,7 @@ void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
|||||||
|
|
||||||
// check remaining buffer size
|
// check remaining buffer size
|
||||||
if (nface*50 != buffer_sz-84) {
|
if (nface*50 != buffer_sz-84) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this,
|
throw mjCError(this,
|
||||||
"STL file '%s' has wrong size; perhaps this is an ASCII file?",
|
"STL file '%s' has wrong size; perhaps this is an ASCII file?",
|
||||||
filename.c_str());
|
filename.c_str());
|
||||||
@@ -765,19 +749,13 @@ void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
|||||||
float* v = (float*)(stl+50*i+12*(j+1));
|
float* v = (float*)(stl+50*i+12*(j+1));
|
||||||
for (int k=0; k < 3; k++) {
|
for (int k=0; k < 3; k++) {
|
||||||
if (std::isnan(v[k]) || std::isinf(v[k])) {
|
if (std::isnan(v[k]) || std::isinf(v[k])) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "STL file '%s' contains invalid vertices.",
|
throw mjCError(this, "STL file '%s' contains invalid vertices.",
|
||||||
filename.c_str());
|
filename.c_str());
|
||||||
}
|
}
|
||||||
// check if vertex coordinates can be cast to an int safely
|
// check if vertex coordinates can be cast to an int safely
|
||||||
if (fabs(v[k])>pow(2, 30)) {
|
if (fabs(v[k])>pow(2, 30)) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this,
|
throw mjCError(this,
|
||||||
"vertex coordinates in STL file '%s' exceed maximum bounds",
|
"vertex coordinates in STL file '%s' exceed maximum bounds",
|
||||||
filename.c_str());
|
filename.c_str());
|
||||||
@@ -797,56 +775,43 @@ void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// free buffer if allocated here
|
mju_closeResource(r);
|
||||||
if (own_buffer) {
|
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveRepeated();
|
RemoveRepeated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// load MSH binary mesh
|
// load MSH binary mesh
|
||||||
void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
void mjCMesh::LoadMSH(int default_provider) {
|
||||||
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
||||||
|
|
||||||
// make filename
|
// make filename
|
||||||
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
||||||
|
|
||||||
|
mjResource* r = nullptr;
|
||||||
|
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
|
// fall back to OS filesystem
|
||||||
|
if(!default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
|
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get file data in buffer
|
// get file data in buffer
|
||||||
char* buffer = 0;
|
char* buffer = 0;
|
||||||
int buffer_sz = 0;
|
int buffer_sz = mju_readResource(r, (const void**) &buffer);
|
||||||
bool own_buffer = false;
|
|
||||||
if (vfs) {
|
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
|
||||||
if (id>=0) {
|
|
||||||
buffer = (char*)vfs->filedata[id];
|
|
||||||
buffer_sz = vfs->filesize[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not found in vfs, read from file
|
|
||||||
if (!buffer) {
|
|
||||||
buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz);
|
|
||||||
own_buffer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// still not found
|
// still not found
|
||||||
if (!buffer) {
|
if (buffer_sz < 0) {
|
||||||
throw mjCError(this, "could not open MSH file '%s'", filename.c_str());
|
mju_closeResource(r);
|
||||||
|
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||||
} else if (!buffer_sz) {
|
} else if (!buffer_sz) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
throw mjCError(this, "STL file '%s' is empty", filename.c_str());
|
||||||
}
|
|
||||||
throw mjCError(this, "MSH file '%s' is empty", filename.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure header is present
|
// make sure header is present
|
||||||
if (buffer_sz<4*sizeof(int)) {
|
if (buffer_sz<4*sizeof(int)) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "missing header in MSH file '%s'", filename.c_str());
|
throw mjCError(this, "missing header in MSH file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,18 +825,14 @@ void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
|||||||
if (nvert<4 || nface<0 || nnormal<0 || ntexcoord<0 ||
|
if (nvert<4 || nface<0 || nnormal<0 || ntexcoord<0 ||
|
||||||
(nnormal>0 && nnormal!=nvert) ||
|
(nnormal>0 && nnormal!=nvert) ||
|
||||||
(ntexcoord>0 && ntexcoord!=nvert)) {
|
(ntexcoord>0 && ntexcoord!=nvert)) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "invalid sizes in MSH file '%s'", filename.c_str());
|
throw mjCError(this, "invalid sizes in MSH file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// check file size
|
// check file size
|
||||||
if (buffer_sz != 4*sizeof(int) + 3*nvert*sizeof(float) + 3*nnormal*sizeof(float) +
|
if (buffer_sz != 4*sizeof(int) + 3*nvert*sizeof(float) + 3*nnormal*sizeof(float) +
|
||||||
2*ntexcoord*sizeof(float) + 3*nface*sizeof(int)) {
|
2*ntexcoord*sizeof(float) + 3*nface*sizeof(int)) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "unexpected file size in MSH file '%s'", filename.c_str());
|
throw mjCError(this, "unexpected file size in MSH file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -912,10 +873,7 @@ void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// free buffer if allocated here
|
mju_closeResource(r);
|
||||||
if (own_buffer) {
|
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1602,7 +1560,7 @@ mjCSkin::~mjCSkin() {
|
|||||||
|
|
||||||
|
|
||||||
// compiler
|
// compiler
|
||||||
void mjCSkin::Compile(const mjVFS* vfs) {
|
void mjCSkin::Compile(int default_provider) {
|
||||||
|
|
||||||
// load file
|
// load file
|
||||||
if (!file.empty()) {
|
if (!file.empty()) {
|
||||||
@@ -1627,7 +1585,7 @@ void mjCSkin::Compile(const mjVFS* vfs) {
|
|||||||
// load SKN
|
// load SKN
|
||||||
string ext = mjuu_getext(file);
|
string ext = mjuu_getext(file);
|
||||||
if (!strcasecmp(ext.c_str(), ".skn")) {
|
if (!strcasecmp(ext.c_str(), ".skn")) {
|
||||||
LoadSKN(vfs);
|
LoadSKN(default_provider);
|
||||||
} else {
|
} else {
|
||||||
throw mjCError(this, "Unknown skin file type: %s", file.c_str());
|
throw mjCError(this, "Unknown skin file type: %s", file.c_str());
|
||||||
}
|
}
|
||||||
@@ -1749,43 +1707,32 @@ void mjCSkin::Compile(const mjVFS* vfs) {
|
|||||||
|
|
||||||
|
|
||||||
// load skin in SKN BIN format
|
// load skin in SKN BIN format
|
||||||
void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
void mjCSkin::LoadSKN(int default_provider) {
|
||||||
// make filename
|
// make filename
|
||||||
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
||||||
|
|
||||||
// get file data in buffer
|
// get file data in buffer
|
||||||
char* buffer = NULL;
|
mjResource* r = nullptr;
|
||||||
int buffer_sz = 0;
|
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
bool own_buffer = false;
|
if(!default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
if (vfs) {
|
throw mjCError(this, "could not open SKN file '%s'", filename.c_str());
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
}
|
||||||
if (id>=0) {
|
|
||||||
buffer = (char*)vfs->filedata[id];
|
|
||||||
buffer_sz = vfs->filesize[id];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not found in vfs, read from file
|
char* buffer = 0;
|
||||||
if (!buffer) {
|
int buffer_sz = mju_readResource(r, (const void**) &buffer);
|
||||||
buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz);
|
|
||||||
own_buffer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// still not found
|
if (buffer_sz < 0) {
|
||||||
if (!buffer) {
|
mju_closeResource(r);
|
||||||
throw mjCError(this, "could not open SKN file '%s'", filename.c_str());
|
throw mjCError(this, "could not open SKN file '%s'", filename.c_str());
|
||||||
} else if (!buffer_sz) {
|
} else if (!buffer_sz) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "SKN file '%s' is empty", filename.c_str());
|
throw mjCError(this, "SKN file '%s' is empty", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure header is present
|
// make sure header is present
|
||||||
if (buffer_sz<16) {
|
if (buffer_sz<16) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "missing header in SKN file '%s'", filename.c_str());
|
throw mjCError(this, "missing header in SKN file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1797,17 +1744,13 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
|||||||
|
|
||||||
// negative sizes not allowed
|
// negative sizes not allowed
|
||||||
if (nvert<0 || ntexcoord<0 || nface<0 || nbone<0) {
|
if (nvert<0 || ntexcoord<0 || nface<0 || nbone<0) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "negative size in header of SKN file '%s'", filename.c_str());
|
throw mjCError(this, "negative size in header of SKN file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we have data for vert, texcoord, face
|
// make sure we have data for vert, texcoord, face
|
||||||
if (buffer_sz < 16 + 12*nvert + 8*ntexcoord + 12*nface) {
|
if (buffer_sz < 16 + 12*nvert + 8*ntexcoord + 12*nface) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "insufficient data in SKN file '%s'", filename.c_str());
|
throw mjCError(this, "insufficient data in SKN file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1847,9 +1790,7 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
|||||||
for (int i=0; i<nbone; i++) {
|
for (int i=0; i<nbone; i++) {
|
||||||
// check size
|
// check size
|
||||||
if (buffer_sz/4-4-cnt < 18) {
|
if (buffer_sz/4-4-cnt < 18) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "insufficient data in SKN file '%s', bone %d", filename.c_str(), i);
|
throw mjCError(this, "insufficient data in SKN file '%s', bone %d", filename.c_str(), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1874,18 +1815,14 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
|||||||
|
|
||||||
// check for negative
|
// check for negative
|
||||||
if (vcount<1) {
|
if (vcount<1) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "vertex count must be positive in SKN file '%s', bone %d",
|
throw mjCError(this, "vertex count must be positive in SKN file '%s', bone %d",
|
||||||
filename.c_str(), i);
|
filename.c_str(), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check size
|
// check size
|
||||||
if (buffer_sz/4-4-cnt < 2*vcount) {
|
if (buffer_sz/4-4-cnt < 2*vcount) {
|
||||||
if (own_buffer) {
|
mju_closeResource(r);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "insufficient vertex data in SKN file '%s', bone %d",
|
throw mjCError(this, "insufficient vertex data in SKN file '%s', bone %d",
|
||||||
filename.c_str(), i);
|
filename.c_str(), i);
|
||||||
}
|
}
|
||||||
@@ -1901,10 +1838,7 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
|||||||
cnt += vcount;
|
cnt += vcount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// free buffer if allocated here
|
mju_closeResource(r);
|
||||||
if (own_buffer) {
|
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check final size
|
// check final size
|
||||||
if (buffer_sz != 16+4*cnt) {
|
if (buffer_sz != 16+4*cnt) {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "engine/engine_macro.h"
|
#include "engine/engine_macro.h"
|
||||||
#include "engine/engine_plugin.h"
|
#include "engine/engine_plugin.h"
|
||||||
#include "engine/engine_setconst.h"
|
#include "engine/engine_setconst.h"
|
||||||
|
#include "engine/engine_resource.h"
|
||||||
#include "engine/engine_support.h"
|
#include "engine/engine_support.h"
|
||||||
#include "engine/engine_util_blas.h"
|
#include "engine/engine_util_blas.h"
|
||||||
#include "engine/engine_util_errmem.h"
|
#include "engine/engine_util_errmem.h"
|
||||||
@@ -2392,7 +2393,7 @@ static void warninghandler(const char* msg) {
|
|||||||
|
|
||||||
|
|
||||||
// compiler
|
// compiler
|
||||||
mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
mjModel* mjCModel::Compile(int default_provider) {
|
||||||
// The volatile keyword is necessary to prevent a possible memory leak due to
|
// The volatile keyword is necessary to prevent a possible memory leak due to
|
||||||
// an interaction between longjmp and compiler optimization. Specifically, at
|
// an interaction between longjmp and compiler optimization. Specifically, at
|
||||||
// the point where the setjmp takes places, these pointers have never been
|
// the point where the setjmp takes places, these pointers have never been
|
||||||
@@ -2423,7 +2424,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
|||||||
// TryCompile resulted in an mju_error which was converted to a longjmp.
|
// TryCompile resulted in an mju_error which was converted to a longjmp.
|
||||||
throw mjCError(0, "engine error: %s", errortext);
|
throw mjCError(0, "engine error: %s", errortext);
|
||||||
}
|
}
|
||||||
TryCompile(*const_cast<mjModel**>(&m), *const_cast<mjData**>(&data), vfs);
|
TryCompile(*const_cast<mjModel**>(&m), *const_cast<mjData**>(&data), default_provider);
|
||||||
} catch (mjCError err) {
|
} catch (mjCError err) {
|
||||||
// deallocate everything allocated in Compile
|
// deallocate everything allocated in Compile
|
||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
@@ -2449,7 +2450,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
void mjCModel::TryCompile(mjModel*& m, mjData*& d, int default_provider) {
|
||||||
// check if nan test works
|
// check if nan test works
|
||||||
double test = mjNAN;
|
double test = mjNAN;
|
||||||
if (mjuu_defined(test)) {
|
if (mjuu_defined(test)) {
|
||||||
@@ -2526,7 +2527,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
|||||||
|
|
||||||
// compile meshes (needed for geom compilation)
|
// compile meshes (needed for geom compilation)
|
||||||
for (int i=0; i<meshes.size(); i++) {
|
for (int i=0; i<meshes.size(); i++) {
|
||||||
meshes[i]->Compile(vfs);
|
meshes[i]->Compile(default_provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// automatically set nuser fields
|
// automatically set nuser fields
|
||||||
@@ -2585,9 +2586,9 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compile all other objects except for keyframes
|
// compile all other objects except for keyframes
|
||||||
for (int i=0; i<skins.size(); i++) skins[i]->Compile(vfs);
|
for (int i=0; i<skins.size(); i++) skins[i]->Compile(default_provider);
|
||||||
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(vfs);
|
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(default_provider);
|
||||||
for (int i=0; i<textures.size(); i++) textures[i]->Compile(vfs);
|
for (int i=0; i<textures.size(); i++) textures[i]->Compile(default_provider);
|
||||||
for (int i=0; i<materials.size(); i++) materials[i]->Compile();
|
for (int i=0; i<materials.size(); i++) materials[i]->Compile();
|
||||||
for (int i=0; i<pairs.size(); i++) pairs[i]->Compile();
|
for (int i=0; i<pairs.size(); i++) pairs[i]->Compile();
|
||||||
for (int i=0; i<excludes.size(); i++) excludes[i]->Compile();
|
for (int i=0; i<excludes.size(); i++) excludes[i]->Compile();
|
||||||
|
|||||||
+24
-23
@@ -62,32 +62,32 @@ class mjCModel {
|
|||||||
friend class mjXWriter;
|
friend class mjXWriter;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
mjCModel(); // constructor
|
mjCModel(); // constructor
|
||||||
~mjCModel(); // destructor
|
~mjCModel(); // destructor
|
||||||
|
|
||||||
mjModel* Compile(const mjVFS* vfs = 0); // COMPILER: construct mjModel
|
mjModel* Compile(int default_provider = 0); // COMPILER: construct mjModel
|
||||||
bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back
|
bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back
|
||||||
void FuseStatic(void); // fuse static bodies with parent
|
void FuseStatic(void); // fuse static bodies with parent
|
||||||
void FuseReindex(mjCBody* body); // reindex elements during fuse
|
void FuseReindex(mjCBody* body); // reindex elements during fuse
|
||||||
|
|
||||||
|
|
||||||
//------------------------ API for adding model elements
|
//------------------------ API for adding model elements
|
||||||
mjCMesh* AddMesh(mjCDef* def = 0); // mesh
|
mjCMesh* AddMesh(mjCDef* def = 0); // mesh
|
||||||
mjCSkin* AddSkin(void); // skin
|
mjCSkin* AddSkin(void); // skin
|
||||||
mjCHField* AddHField(void); // heightfield
|
mjCHField* AddHField(void); // heightfield
|
||||||
mjCTexture* AddTexture(void); // texture
|
mjCTexture* AddTexture(void); // texture
|
||||||
mjCMaterial*AddMaterial(mjCDef* def = 0); // material
|
mjCMaterial* AddMaterial(mjCDef* def = 0); // material
|
||||||
mjCPair* AddPair(mjCDef* def = 0); // geom pair for inclusion
|
mjCPair* AddPair(mjCDef* def = 0); // geom pair for inclusion
|
||||||
mjCBodyPair*AddExclude(void); // body pair for exclusion
|
mjCBodyPair* AddExclude(void); // body pair for exclusion
|
||||||
mjCEquality*AddEquality(mjCDef* def = 0); // equality constraint
|
mjCEquality* AddEquality(mjCDef* def = 0); // equality constraint
|
||||||
mjCTendon* AddTendon(mjCDef* def = 0); // tendon
|
mjCTendon* AddTendon(mjCDef* def = 0); // tendon
|
||||||
mjCActuator*AddActuator(mjCDef* def = 0); // actuator
|
mjCActuator* AddActuator(mjCDef* def = 0); // actuator
|
||||||
mjCSensor* AddSensor(void); // sensor
|
mjCSensor* AddSensor(void); // sensor
|
||||||
mjCNumeric* AddNumeric(void); // custom numeric
|
mjCNumeric* AddNumeric(void); // custom numeric
|
||||||
mjCText* AddText(void); // custom text
|
mjCText* AddText(void); // custom text
|
||||||
mjCTuple* AddTuple(void); // custom tuple
|
mjCTuple* AddTuple(void); // custom tuple
|
||||||
mjCKey* AddKey(void); // keyframe
|
mjCKey* AddKey(void); // keyframe
|
||||||
mjCPlugin* AddPlugin(void); // plugin instance
|
mjCPlugin* AddPlugin(void); // plugin instance
|
||||||
|
|
||||||
//------------------------ API for access to model elements (outside tree)
|
//------------------------ API for access to model elements (outside tree)
|
||||||
int NumObjects(mjtObj type); // number of objects in specified list
|
int NumObjects(mjtObj type); // number of objects in specified list
|
||||||
@@ -163,7 +163,8 @@ class mjCModel {
|
|||||||
int nuser_sensor; // number of mjtNums in sensor_user
|
int nuser_sensor; // number of mjtNums in sensor_user
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs);
|
void TryCompile(mjModel*& m, mjData*& d, int default_provider);
|
||||||
|
mjModel* _Compile(int default_provider);
|
||||||
|
|
||||||
void Clear(void); // clear objects allocated by Compile
|
void Clear(void); // clear objects allocated by Compile
|
||||||
|
|
||||||
|
|||||||
+82
-108
@@ -29,7 +29,7 @@
|
|||||||
#include "cc/array_safety.h"
|
#include "cc/array_safety.h"
|
||||||
#include "engine/engine_core_smooth.h"
|
#include "engine/engine_core_smooth.h"
|
||||||
#include "engine/engine_crossplatform.h"
|
#include "engine/engine_crossplatform.h"
|
||||||
#include "engine/engine_file.h"
|
#include "engine/engine_resource.h"
|
||||||
#include "engine/engine_io.h"
|
#include "engine/engine_io.h"
|
||||||
#include "engine/engine_macro.h"
|
#include "engine/engine_macro.h"
|
||||||
#include "engine/engine_passive.h"
|
#include "engine/engine_passive.h"
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
#include "engine/engine_util_misc.h"
|
#include "engine/engine_util_misc.h"
|
||||||
#include "engine/engine_util_solve.h"
|
#include "engine/engine_util_solve.h"
|
||||||
#include "engine/engine_util_spatial.h"
|
#include "engine/engine_util_spatial.h"
|
||||||
#include "engine/engine_vfs.h"
|
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "user/user_util.h"
|
#include "user/user_util.h"
|
||||||
|
|
||||||
@@ -1979,33 +1978,27 @@ mjCHField::~mjCHField() {
|
|||||||
|
|
||||||
|
|
||||||
// load elevation data from custom format
|
// load elevation data from custom format
|
||||||
void mjCHField::LoadCustom(string filename, const mjVFS* vfs) {
|
void mjCHField::LoadCustom(string filename, int default_provider) {
|
||||||
// get file data in buffer
|
// get file data in buffer
|
||||||
void* buffer = 0;
|
const void* buffer = 0;
|
||||||
int buffer_sz = 0, flag_existing = 0;
|
mjResource* resource = nullptr;
|
||||||
if (vfs) {
|
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
if (id>=0) {
|
// default to OS filesystem
|
||||||
buffer = vfs->filedata[id];
|
if (!default_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
buffer_sz = vfs->filesize[id];
|
throw mjCError(this, "could not open hfield file '%s'", filename.c_str());
|
||||||
flag_existing = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not found in vfs, read from file
|
int buffer_sz = mju_readResource(resource, &buffer);
|
||||||
if (!buffer) {
|
|
||||||
buffer = mju_fileToMemory(filename.c_str(), &buffer_sz);
|
|
||||||
}
|
|
||||||
|
|
||||||
// still not found
|
// still not found
|
||||||
if (!buffer || !buffer_sz) {
|
if (!buffer || buffer_sz < 1) {
|
||||||
throw mjCError(this, "could not open hfield file '%s'", filename.c_str());
|
throw mjCError(this, "could not open hfield file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer_sz < 2*sizeof(int)) {
|
if (buffer_sz < 2*sizeof(int)) {
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
throw mjCError(this, "hfield missing header '%s'", filename.c_str());
|
throw mjCError(this, "hfield missing header '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2016,76 +2009,69 @@ void mjCHField::LoadCustom(string filename, const mjVFS* vfs) {
|
|||||||
|
|
||||||
// check dimensions
|
// check dimensions
|
||||||
if (nrow<1 || ncol<1) {
|
if (nrow<1 || ncol<1) {
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "non-positive hfield dimensions in file '%s'", filename.c_str());
|
throw mjCError(this, "non-positive hfield dimensions in file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// check buffer size
|
// check buffer size
|
||||||
if (buffer_sz != nrow*ncol*sizeof(float)+8) {
|
if (buffer_sz != nrow*ncol*sizeof(float)+8) {
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "unexpected file size in file '%s'", filename.c_str());
|
throw mjCError(this, "unexpected file size in file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate
|
// allocate
|
||||||
data = (float*) mju_malloc(nrow*ncol*sizeof(float));
|
data = (float*) mju_malloc(nrow*ncol*sizeof(float));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "could not allocate buffers in hfield");
|
throw mjCError(this, "could not allocate buffers in hfield");
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy data
|
// copy data
|
||||||
memcpy(data, (void*)(pint+2), nrow*ncol*sizeof(float));
|
memcpy(data, (void*)(pint+2), nrow*ncol*sizeof(float));
|
||||||
|
|
||||||
// free buffer if allocated here
|
// close file
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// load elevation data from PNG format
|
// load elevation data from PNG format
|
||||||
void mjCHField::LoadPNG(string filename, const mjVFS* vfs) {
|
void mjCHField::LoadPNG(string filename, int default_provider) {
|
||||||
// determine data source
|
// determine data source
|
||||||
const unsigned char* inbuffer = 0;
|
const void* inbuffer = 0;
|
||||||
size_t inbuffer_sz = 0;
|
mjResource* resource = nullptr;
|
||||||
if (vfs) {
|
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
if (id>=0) {
|
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||||
inbuffer = (const unsigned char*)vfs->filedata[id];
|
}
|
||||||
inbuffer_sz = (size_t)vfs->filesize[id];
|
|
||||||
}
|
int inbuffer_sz = mju_readResource(resource, &inbuffer);
|
||||||
|
|
||||||
|
// still not found
|
||||||
|
if (!inbuffer || inbuffer_sz < 1) {
|
||||||
|
mju_closeResource(resource);
|
||||||
|
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// load PNG from file or memory
|
// load PNG from file or memory
|
||||||
unsigned int w, h, err;
|
unsigned int w, h, err;
|
||||||
std::vector<unsigned char> image;
|
std::vector<unsigned char> image;
|
||||||
if (inbuffer_sz) {
|
err = lodepng::decode(image, w, h, (const unsigned char*) inbuffer, inbuffer_sz, LCT_GREY, 8);
|
||||||
err = lodepng::decode(image, w, h, inbuffer, inbuffer_sz, LCT_GREY, 8);
|
|
||||||
} else {
|
|
||||||
err = lodepng::decode(image, w, h, filename, LCT_GREY, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
// check
|
||||||
if (err) {
|
if (err) {
|
||||||
|
mju_closeResource(resource);
|
||||||
throw mjCError(this, "PNG load error '%s' in hfield id = %d", lodepng_error_text(err), id);
|
throw mjCError(this, "PNG load error '%s' in hfield id = %d", lodepng_error_text(err), id);
|
||||||
}
|
}
|
||||||
if (!w || !h) {
|
if (!w || !h) {
|
||||||
|
mju_closeResource(resource);
|
||||||
throw mjCError(this, "Zero dimension in PNG hfield '%s' (id = %d)", name.c_str(), id);
|
throw mjCError(this, "Zero dimension in PNG hfield '%s' (id = %d)", name.c_str(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate
|
// allocate
|
||||||
data = (float*) mju_malloc(w*h*sizeof(float));
|
data = (float*) mju_malloc(w*h*sizeof(float));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
mju_closeResource(resource);
|
||||||
throw mjCError(this, "could not allocate buffers in hfield");
|
throw mjCError(this, "could not allocate buffers in hfield");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2097,12 +2083,13 @@ void mjCHField::LoadPNG(string filename, const mjVFS* vfs) {
|
|||||||
data[c+(nrow-1-r)*ncol] = (float)image[c+r*ncol];
|
data[c+(nrow-1-r)*ncol] = (float)image[c+r*ncol];
|
||||||
}
|
}
|
||||||
image.clear();
|
image.clear();
|
||||||
|
mju_closeResource(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// compiler
|
// compiler
|
||||||
void mjCHField::Compile(const mjVFS* vfs) {
|
void mjCHField::Compile(int default_provider) {
|
||||||
// check size parameters
|
// check size parameters
|
||||||
for (int i=0; i<4; i++)
|
for (int i=0; i<4; i++)
|
||||||
if (size[i]<=0)
|
if (size[i]<=0)
|
||||||
@@ -2128,9 +2115,9 @@ void mjCHField::Compile(const mjVFS* vfs) {
|
|||||||
// load depending on format
|
// load depending on format
|
||||||
string ext = mjuu_getext(filename);
|
string ext = mjuu_getext(filename);
|
||||||
if (!strcasecmp(ext.c_str(), ".png")) {
|
if (!strcasecmp(ext.c_str(), ".png")) {
|
||||||
LoadPNG(filename, vfs);
|
LoadPNG(filename, default_provider);
|
||||||
} else {
|
} else {
|
||||||
LoadCustom(filename, vfs);
|
LoadCustom(filename, default_provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2441,27 +2428,27 @@ void mjCTexture::BuiltinCube(void) {
|
|||||||
|
|
||||||
|
|
||||||
// load PNG file
|
// load PNG file
|
||||||
void mjCTexture::LoadPNG(string filename, const mjVFS* vfs,
|
void mjCTexture::LoadPNG(string filename, int default_provider,
|
||||||
std::vector<unsigned char>& image,
|
std::vector<unsigned char>& image,
|
||||||
unsigned int& w, unsigned int& h) {
|
unsigned int& w, unsigned int& h) {
|
||||||
// determine data source
|
const void* inbuffer = 0;
|
||||||
const unsigned char* inbuffer = 0;
|
mjResource* resource = nullptr;
|
||||||
size_t inbuffer_sz = 0;
|
|
||||||
if (vfs) {
|
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||||
if (id>=0) {
|
}
|
||||||
inbuffer = (const unsigned char*)vfs->filedata[id];
|
|
||||||
inbuffer_sz = (size_t)vfs->filesize[id];
|
int inbuffer_sz = mju_readResource(resource, &inbuffer);
|
||||||
}
|
|
||||||
|
// still not found
|
||||||
|
if (!inbuffer || inbuffer_sz < 1) {
|
||||||
|
mju_closeResource(resource);
|
||||||
|
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// load PNG from file or memory
|
// load PNG from file or memory
|
||||||
unsigned int err;
|
unsigned int err = lodepng::decode(image, w, h, (const unsigned char*) inbuffer, inbuffer_sz, LCT_RGB, 8);
|
||||||
if (inbuffer_sz) {
|
mju_closeResource(resource);
|
||||||
err = lodepng::decode(image, w, h, inbuffer, inbuffer_sz, LCT_RGB, 8);
|
|
||||||
} else {
|
|
||||||
err = lodepng::decode(image, w, h, filename, LCT_RGB, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
// check
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -2476,28 +2463,24 @@ void mjCTexture::LoadPNG(string filename, const mjVFS* vfs,
|
|||||||
|
|
||||||
|
|
||||||
// load custom file
|
// load custom file
|
||||||
void mjCTexture::LoadCustom(string filename, const mjVFS* vfs,
|
void mjCTexture::LoadCustom(string filename, int default_provider,
|
||||||
std::vector<unsigned char>& image,
|
std::vector<unsigned char>& image,
|
||||||
unsigned int& w, unsigned int& h) {
|
unsigned int& w, unsigned int& h) {
|
||||||
// get file data in buffer
|
const void* buffer = 0;
|
||||||
void* buffer = 0;
|
mjResource* resource = nullptr;
|
||||||
int buffer_sz = 0, flag_existing = 0;
|
|
||||||
if (vfs) {
|
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
// default to OS filesystem
|
||||||
if (id>=0) {
|
if (!default_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
buffer = vfs->filedata[id];
|
throw mjCError(this, "could not open texture file '%s'", filename.c_str());
|
||||||
buffer_sz = vfs->filesize[id];
|
|
||||||
flag_existing = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not found in vfs, read from file
|
int buffer_sz = mju_readResource(resource, &buffer);
|
||||||
if (!buffer) {
|
|
||||||
buffer = mju_fileToMemory(filename.c_str(), &buffer_sz);
|
|
||||||
}
|
|
||||||
|
|
||||||
// still not found
|
// still not found
|
||||||
if (!buffer || !buffer_sz) {
|
if (!buffer || buffer_sz < 0) {
|
||||||
|
mju_closeResource(resource);
|
||||||
throw mjCError(this, "could not open texture file '%s'", filename.c_str());
|
throw mjCError(this, "could not open texture file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2508,20 +2491,14 @@ void mjCTexture::LoadCustom(string filename, const mjVFS* vfs,
|
|||||||
|
|
||||||
// check dimensions
|
// check dimensions
|
||||||
if (w<1 || h<1) {
|
if (w<1 || h<1) {
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "Non-PNG texture, assuming custom binary file format,\n"
|
throw mjCError(this, "Non-PNG texture, assuming custom binary file format,\n"
|
||||||
"non-positive texture dimensions in file '%s'", filename.c_str());
|
"non-positive texture dimensions in file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// check buffer size
|
// check buffer size
|
||||||
if (buffer_sz != 2*sizeof(int) + w*h*3*sizeof(char)) {
|
if (buffer_sz != 2*sizeof(int) + w*h*3*sizeof(char)) {
|
||||||
if (!flag_existing) {
|
mju_closeResource(resource);
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mjCError(this, "Non-PNG texture, assuming custom binary file format,\n"
|
throw mjCError(this, "Non-PNG texture, assuming custom binary file format,\n"
|
||||||
"unexpected file size in file '%s'", filename.c_str());
|
"unexpected file size in file '%s'", filename.c_str());
|
||||||
}
|
}
|
||||||
@@ -2530,24 +2507,21 @@ void mjCTexture::LoadCustom(string filename, const mjVFS* vfs,
|
|||||||
image.resize(w*h*3);
|
image.resize(w*h*3);
|
||||||
memcpy(image.data(), (void*)(pint+2), w*h*3*sizeof(char));
|
memcpy(image.data(), (void*)(pint+2), w*h*3*sizeof(char));
|
||||||
|
|
||||||
// free buffer if allocated here
|
mju_closeResource(resource);
|
||||||
if (!flag_existing) {
|
|
||||||
mju_free(buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// load from PNG or custom file, flip if specified
|
// load from PNG or custom file, flip if specified
|
||||||
void mjCTexture::LoadFlip(string filename, const mjVFS* vfs,
|
void mjCTexture::LoadFlip(string filename, int default_provider,
|
||||||
std::vector<unsigned char>& image,
|
std::vector<unsigned char>& image,
|
||||||
unsigned int& w, unsigned int& h) {
|
unsigned int& w, unsigned int& h) {
|
||||||
// dispatch to PNG or Custom loaded
|
// dispatch to PNG or Custom loaded
|
||||||
string ext = mjuu_getext(filename);
|
string ext = mjuu_getext(filename);
|
||||||
if (!strcasecmp(ext.c_str(), ".png")) {
|
if (!strcasecmp(ext.c_str(), ".png")) {
|
||||||
LoadPNG(filename, vfs, image, w, h);
|
LoadPNG(filename, default_provider, image, w, h);
|
||||||
} else {
|
} else {
|
||||||
LoadCustom(filename, vfs, image, w, h);
|
LoadCustom(filename, default_provider, image, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// horizontal flip
|
// horizontal flip
|
||||||
@@ -2598,11 +2572,11 @@ void mjCTexture::LoadFlip(string filename, const mjVFS* vfs,
|
|||||||
|
|
||||||
|
|
||||||
// load 2D
|
// load 2D
|
||||||
void mjCTexture::Load2D(string filename, const mjVFS* vfs) {
|
void mjCTexture::Load2D(string filename, int default_provider) {
|
||||||
// load PNG or custom
|
// load PNG or custom
|
||||||
unsigned int w, h;
|
unsigned int w, h;
|
||||||
std::vector<unsigned char> image;
|
std::vector<unsigned char> image;
|
||||||
LoadFlip(filename, vfs, image, w, h);
|
LoadFlip(filename, default_provider, image, w, h);
|
||||||
|
|
||||||
// assign size
|
// assign size
|
||||||
width = w;
|
width = w;
|
||||||
@@ -2621,7 +2595,7 @@ void mjCTexture::Load2D(string filename, const mjVFS* vfs) {
|
|||||||
|
|
||||||
|
|
||||||
// load cube or skybox from single file (repeated or grid)
|
// load cube or skybox from single file (repeated or grid)
|
||||||
void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
|
void mjCTexture::LoadCubeSingle(string filename, int default_provider) {
|
||||||
// check gridsize
|
// check gridsize
|
||||||
if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) {
|
if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) {
|
||||||
throw mjCError(this,
|
throw mjCError(this,
|
||||||
@@ -2632,7 +2606,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
|
|||||||
// load PNG or custom
|
// load PNG or custom
|
||||||
unsigned int w, h;
|
unsigned int w, h;
|
||||||
std::vector<unsigned char> image;
|
std::vector<unsigned char> image;
|
||||||
LoadFlip(filename, vfs, image, w, h);
|
LoadFlip(filename, default_provider, image, w, h);
|
||||||
|
|
||||||
// check gridsize for compatibility
|
// check gridsize for compatibility
|
||||||
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
|
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
|
||||||
@@ -2721,7 +2695,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
|
|||||||
|
|
||||||
|
|
||||||
// load cube or skybox from separate file
|
// load cube or skybox from separate file
|
||||||
void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
|
void mjCTexture::LoadCubeSeparate(int default_provider) {
|
||||||
// keep track of which faces were defined
|
// keep track of which faces were defined
|
||||||
int loaded[6] = {0, 0, 0, 0, 0, 0};
|
int loaded[6] = {0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
@@ -2739,7 +2713,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
|
|||||||
// load PNG or custom
|
// load PNG or custom
|
||||||
unsigned int w, h;
|
unsigned int w, h;
|
||||||
std::vector<unsigned char> image;
|
std::vector<unsigned char> image;
|
||||||
LoadFlip(filename, vfs, image, w, h);
|
LoadFlip(filename, default_provider, image, w, h);
|
||||||
|
|
||||||
// PNG must be square
|
// PNG must be square
|
||||||
if (w!=h) {
|
if (w!=h) {
|
||||||
@@ -2793,7 +2767,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
|
|||||||
|
|
||||||
|
|
||||||
// compiler
|
// compiler
|
||||||
void mjCTexture::Compile(const mjVFS* vfs) {
|
void mjCTexture::Compile(int default_provider) {
|
||||||
// builtin
|
// builtin
|
||||||
if (builtin!=mjBUILTIN_NONE) {
|
if (builtin!=mjBUILTIN_NONE) {
|
||||||
// check size
|
// check size
|
||||||
@@ -2836,9 +2810,9 @@ void mjCTexture::Compile(const mjVFS* vfs) {
|
|||||||
|
|
||||||
// dispatch
|
// dispatch
|
||||||
if (type==mjTEXTURE_2D) {
|
if (type==mjTEXTURE_2D) {
|
||||||
Load2D(filename, vfs);
|
Load2D(filename, default_provider);
|
||||||
} else {
|
} else {
|
||||||
LoadCubeSingle(filename, vfs);
|
LoadCubeSingle(filename, default_provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2866,7 +2840,7 @@ void mjCTexture::Compile(const mjVFS* vfs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only cube and skybox
|
// only cube and skybox
|
||||||
LoadCubeSeparate(vfs);
|
LoadCubeSeparate(default_provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure someone allocated data; SHOULD NOT OCCUR
|
// make sure someone allocated data; SHOULD NOT OCCUR
|
||||||
|
|||||||
+16
-16
@@ -512,10 +512,10 @@ class mjCMesh: public mjCBase {
|
|||||||
private:
|
private:
|
||||||
mjCMesh(mjCModel* = 0, mjCDef* = 0); // constructor
|
mjCMesh(mjCModel* = 0, mjCDef* = 0); // constructor
|
||||||
~mjCMesh(); // destructor
|
~mjCMesh(); // destructor
|
||||||
void Compile(const mjVFS* vfs); // compiler
|
void Compile(int default_provider); // compiler
|
||||||
void LoadOBJ(const mjVFS* vfs); // load mesh in wavefront OBJ format
|
void LoadOBJ(int default_provider); // load mesh in wavefront OBJ format
|
||||||
void LoadSTL(const mjVFS* vfs); // load mesh in STL BIN format
|
void LoadSTL(int default_provider); // load mesh in STL BIN format
|
||||||
void LoadMSH(const mjVFS* vfs); // load mesh in MSH BIN format
|
void LoadMSH(int default_provider); // load mesh in MSH BIN format
|
||||||
void MakeGraph(void); // make graph of convex hull
|
void MakeGraph(void); // make graph of convex hull
|
||||||
void CopyGraph(void); // copy graph into face data
|
void CopyGraph(void); // copy graph into face data
|
||||||
void MakeNormal(void); // compute vertex normals
|
void MakeNormal(void); // compute vertex normals
|
||||||
@@ -590,8 +590,8 @@ class mjCSkin: public mjCBase {
|
|||||||
private:
|
private:
|
||||||
mjCSkin(mjCModel* = 0); // constructor
|
mjCSkin(mjCModel* = 0); // constructor
|
||||||
~mjCSkin(); // destructor
|
~mjCSkin(); // destructor
|
||||||
void Compile(const mjVFS* vfs); // compiler
|
void Compile(int default_provider); // compiler
|
||||||
void LoadSKN(const mjVFS* vfs); // load skin in SKN BIN format
|
void LoadSKN(int default_provider); // load skin in SKN BIN format
|
||||||
|
|
||||||
int matid; // material id
|
int matid; // material id
|
||||||
std::vector<int> bodyid; // body ids
|
std::vector<int> bodyid; // body ids
|
||||||
@@ -616,10 +616,10 @@ class mjCHField : public mjCBase {
|
|||||||
private:
|
private:
|
||||||
mjCHField(mjCModel* model); // constructor
|
mjCHField(mjCModel* model); // constructor
|
||||||
~mjCHField(); // destructor
|
~mjCHField(); // destructor
|
||||||
void Compile(const mjVFS* vfs); // compiler
|
void Compile(int default_provider); // compiler
|
||||||
|
|
||||||
void LoadCustom(std::string filename, const mjVFS* vfs); // load from custom format
|
void LoadCustom(std::string filename, int default_provider); // load from custom format
|
||||||
void LoadPNG(std::string filename, const mjVFS* vfs); // load from PNG format
|
void LoadPNG(std::string filename, int default_provider); // load from PNG format
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -660,22 +660,22 @@ class mjCTexture : public mjCBase {
|
|||||||
private:
|
private:
|
||||||
mjCTexture(mjCModel*); // constructor
|
mjCTexture(mjCModel*); // constructor
|
||||||
~mjCTexture(); // destructior
|
~mjCTexture(); // destructior
|
||||||
void Compile(const mjVFS* vfs); // compiler
|
void Compile(int default_provider); // compiler
|
||||||
|
|
||||||
void Builtin2D(void); // make builtin 2D
|
void Builtin2D(void); // make builtin 2D
|
||||||
void BuiltinCube(void); // make builtin cube
|
void BuiltinCube(void); // make builtin cube
|
||||||
void Load2D(std::string filename, const mjVFS* vfs); // load 2D from file
|
void Load2D(std::string filename, int default_provider); // load 2D from file
|
||||||
void LoadCubeSingle(std::string filename, const mjVFS* vfs); // load cube from single file
|
void LoadCubeSingle(std::string filename, int default_provider); // load cube from single file
|
||||||
void LoadCubeSeparate(const mjVFS* vfs); // load cube from separate files
|
void LoadCubeSeparate(int default_provider); // load cube from separate files
|
||||||
|
|
||||||
void LoadFlip(std::string filename, const mjVFS* vfs, // load and flip
|
void LoadFlip(std::string filename, int default_provider, // load and flip
|
||||||
std::vector<unsigned char>& image,
|
std::vector<unsigned char>& image,
|
||||||
unsigned int& w, unsigned int& h);
|
unsigned int& w, unsigned int& h);
|
||||||
|
|
||||||
void LoadPNG(std::string filename, const mjVFS* vfs,
|
void LoadPNG(std::string filename, int default_provider,
|
||||||
std::vector<unsigned char>& image,
|
std::vector<unsigned char>& image,
|
||||||
unsigned int& w, unsigned int& h);
|
unsigned int& w, unsigned int& h);
|
||||||
void LoadCustom(std::string filename, const mjVFS* vfs,
|
void LoadCustom(std::string filename, int default_provider,
|
||||||
std::vector<unsigned char>& image,
|
std::vector<unsigned char>& image,
|
||||||
unsigned int& w, unsigned int& h);
|
unsigned int& w, unsigned int& h);
|
||||||
|
|
||||||
|
|||||||
+61
-37
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
#include "cc/array_safety.h"
|
#include "cc/array_safety.h"
|
||||||
#include "engine/engine_crossplatform.h"
|
#include "engine/engine_crossplatform.h"
|
||||||
#include "engine/engine_vfs.h"
|
#include "engine/engine_resource.h"
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "user/user_util.h"
|
#include "user/user_util.h"
|
||||||
#include "xml/xml_native_reader.h"
|
#include "xml/xml_native_reader.h"
|
||||||
@@ -129,7 +129,7 @@ bool mjWriteXML(mjCModel* model, string filename, char* error, int error_sz) {
|
|||||||
|
|
||||||
// find include elements recursively, replace them with subtree from xml file
|
// find include elements recursively, replace them with subtree from xml file
|
||||||
static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
|
static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
|
||||||
const mjVFS* vfs, vector<string>& included) {
|
int default_provider, vector<string>& included) {
|
||||||
// include element: process
|
// include element: process
|
||||||
if (!strcasecmp(elem->Value(), "include")) {
|
if (!strcasecmp(elem->Value(), "include")) {
|
||||||
// make sure include has no children
|
// make sure include has no children
|
||||||
@@ -150,23 +150,30 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get data source
|
// get data source
|
||||||
const char* xmlstring = 0;
|
mjResource *resource = nullptr;
|
||||||
int buffer_size = 0;
|
const char* xmlstring = nullptr;
|
||||||
if (vfs) {
|
if ((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
// load from OS filesystem
|
||||||
if (id>=0) {
|
if (!default_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||||
xmlstring = (const char*)vfs->filedata[id];
|
throw mjXError(elem, "Could not open file '%s'", filename.c_str());
|
||||||
buffer_size = vfs->filesize[id];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
|
||||||
|
if (buffer_size < 0) {
|
||||||
|
mju_closeResource(resource);
|
||||||
|
throw mjXError(elem, "Error reading file '%s'", filename.c_str());
|
||||||
|
} else if (!buffer_size) {
|
||||||
|
mju_closeResource(resource);
|
||||||
|
throw mjXError(elem, "Empty file '%s'", filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// load XML file or parse string
|
// load XML file or parse string
|
||||||
XMLDocument doc;
|
XMLDocument doc;
|
||||||
if (xmlstring) {
|
doc.Parse(xmlstring, buffer_size);
|
||||||
doc.Parse(xmlstring, buffer_size);
|
|
||||||
} else {
|
// close resource
|
||||||
doc.LoadFile(filename.c_str());
|
mju_closeResource(resource);
|
||||||
}
|
|
||||||
|
|
||||||
// check error
|
// check error
|
||||||
if (doc.Error()) {
|
if (doc.Error()) {
|
||||||
@@ -208,27 +215,26 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run XMLInclude on first new child
|
// run XMLInclude on first new child
|
||||||
return mjIncludeXML(first->ToElement(), dir, vfs, included);
|
return mjIncludeXML(first->ToElement(), dir, default_provider, included);
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise check all child elements, return self
|
// otherwise check all child elements, return self
|
||||||
else {
|
else {
|
||||||
XMLElement* child = elem->FirstChildElement();
|
XMLElement* child = elem->FirstChildElement();
|
||||||
while (child) {
|
while (child) {
|
||||||
child = mjIncludeXML(child, dir, vfs, included);
|
child = mjIncludeXML(child, dir, default_provider, included);
|
||||||
if (child) {
|
if (child) {
|
||||||
child = child->NextSiblingElement();
|
child = child->NextSiblingElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Main parser function: from file or VFS
|
// Main parser function
|
||||||
mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz) {
|
mjCModel* mjParseXML(const char* filename, int default_provider, char* error, int error_sz) {
|
||||||
LocaleOverride locale_override;
|
LocaleOverride locale_override;
|
||||||
|
|
||||||
// check arguments
|
// check arguments
|
||||||
@@ -236,33 +242,51 @@ mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int er
|
|||||||
if (error) {
|
if (error) {
|
||||||
snprintf(error, error_sz, "mjParseXML: filename argument required\n");
|
snprintf(error, error_sz, "mjParseXML: filename argument required\n");
|
||||||
}
|
}
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear
|
// clear
|
||||||
mjCModel* model = 0;
|
mjCModel* model = 0;
|
||||||
if (error) {
|
if (error) {
|
||||||
error[0] = 0;
|
error[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// get data source
|
// get data source
|
||||||
const char* xmlstring = 0;
|
mjResource* resource = nullptr;
|
||||||
int buffer_size = 0;
|
const char* xmlstring = nullptr;
|
||||||
if (vfs) {
|
if ((resource = mju_openResource(filename, default_provider)) == nullptr) {
|
||||||
int id = mj_findFileVFS(vfs, filename);
|
// load from OS filesystem
|
||||||
if (id>=0) {
|
if (!default_provider || (resource = mju_openResource(filename, 0)) == nullptr) {
|
||||||
xmlstring = (const char*)vfs->filedata[id];
|
if (error) {
|
||||||
buffer_size = vfs->filesize[id];
|
snprintf(error, error_sz, "mjParseXML: could not open file '%s'", filename);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
|
||||||
|
if (buffer_size < 0) {
|
||||||
|
if (error) {
|
||||||
|
snprintf(error, error_sz, "mjParseXML: error reading file '%s'", filename);
|
||||||
|
}
|
||||||
|
mju_closeResource(resource);
|
||||||
|
return nullptr;
|
||||||
|
} else if (!buffer_size) {
|
||||||
|
if (error) {
|
||||||
|
snprintf(error, error_sz, "mjParseXML: empty file '%s'", filename);
|
||||||
|
}
|
||||||
|
mju_closeResource(resource);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// load XML file or parse string
|
// load XML file or parse string
|
||||||
XMLDocument doc;
|
XMLDocument doc;
|
||||||
if (xmlstring) {
|
doc.Parse(xmlstring, buffer_size);
|
||||||
doc.Parse(xmlstring, buffer_size);
|
|
||||||
} else {
|
// close resource
|
||||||
doc.LoadFile(filename);
|
mju_closeResource(resource);
|
||||||
}
|
|
||||||
|
|
||||||
// error checking
|
// error checking
|
||||||
if (doc.Error()) {
|
if (doc.Error()) {
|
||||||
@@ -270,14 +294,14 @@ mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int er
|
|||||||
snprintf(error, error_sz, "XML parse error %d:\n%s\n",
|
snprintf(error, error_sz, "XML parse error %d:\n%s\n",
|
||||||
doc.ErrorID(), doc.ErrorStr());
|
doc.ErrorID(), doc.ErrorStr());
|
||||||
}
|
}
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get top-level element
|
// get top-level element
|
||||||
XMLElement* root = doc.RootElement();
|
XMLElement* root = doc.RootElement();
|
||||||
if (!root) {
|
if (!root) {
|
||||||
mjCopyError(error, "XML root element not found", error_sz);
|
mjCopyError(error, "XML root element not found", error_sz);
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create model, set filedir
|
// create model, set filedir
|
||||||
@@ -290,7 +314,7 @@ mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int er
|
|||||||
// find include elements, replace them with subtree from xml file
|
// find include elements, replace them with subtree from xml file
|
||||||
vector<string> included;
|
vector<string> included;
|
||||||
included.push_back(filename);
|
included.push_back(filename);
|
||||||
mjIncludeXML(root, model->modelfiledir, vfs, included);
|
mjIncludeXML(root, model->modelfiledir, default_provider, included);
|
||||||
|
|
||||||
// parse MuJoCo model
|
// parse MuJoCo model
|
||||||
mjXReader parser;
|
mjXReader parser;
|
||||||
@@ -314,7 +338,7 @@ mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int er
|
|||||||
catch (mjXError err) {
|
catch (mjXError err) {
|
||||||
mjCopyError(error, err.message, error_sz);
|
mjCopyError(error, err.message, error_sz);
|
||||||
delete model;
|
delete model;
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
|
|||||||
+2
-2
@@ -25,8 +25,8 @@
|
|||||||
// Main writer function
|
// Main writer function
|
||||||
bool mjWriteXML(mjCModel* model, std::string filename, char* error, int error_sz);
|
bool mjWriteXML(mjCModel* model, std::string filename, char* error, int error_sz);
|
||||||
|
|
||||||
// Main parser function: from file or VFS
|
// Main parser function
|
||||||
mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz);
|
mjCModel* mjParseXML(const char* filename, int default_provider, char* error, int error_sz);
|
||||||
|
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_XML_XML_H_
|
#endif // MUJOCO_SRC_XML_XML_H_
|
||||||
|
|||||||
+34
-10
@@ -20,6 +20,8 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
|
#include "engine/engine_resource.h"
|
||||||
|
#include "engine/engine_vfs.h"
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "xml/xml.h"
|
#include "xml/xml.h"
|
||||||
#include "xml/xml_native_reader.h"
|
#include "xml/xml_native_reader.h"
|
||||||
@@ -80,26 +82,24 @@ void mj_deactivate(void) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// parse XML file in MJCF or URDF format, compile it, return low-level model
|
// mj_loadXML helper function
|
||||||
// if vfs is not NULL, look up files in vfs before reading from disk
|
mjModel* _loadXML(const char* filename, int default_provider,
|
||||||
// error can be NULL; otherwise assumed to have size error_sz
|
char* error, int error_sz) {
|
||||||
mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
|
|
||||||
char* error, int error_sz) {
|
|
||||||
// serialize access to themodel
|
// serialize access to themodel
|
||||||
std::lock_guard<std::mutex> lock(themutex);
|
std::lock_guard<std::mutex> lock(themutex);
|
||||||
|
|
||||||
// parse new model
|
// parse new model
|
||||||
mjCModel* newmodel = mjParseXML(filename, vfs, error, error_sz);
|
mjCModel* newmodel = mjParseXML(filename, default_provider, error, error_sz);
|
||||||
if (!newmodel) {
|
if (!newmodel) {
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// compile new model
|
// compile new model
|
||||||
mjModel* m = newmodel->Compile(vfs);
|
mjModel* m = newmodel->Compile(default_provider);
|
||||||
if (!m) {
|
if (!m) {
|
||||||
mjCopyError(error, newmodel->GetError().message, error_sz);
|
mjCopyError(error, newmodel->GetError().message, error_sz);
|
||||||
delete newmodel;
|
delete newmodel;
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear old and assign new
|
// clear old and assign new
|
||||||
@@ -110,7 +110,7 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
|
|||||||
if (themodel.model->GetError().warning) {
|
if (themodel.model->GetError().warning) {
|
||||||
mjCopyError(error, themodel.model->GetError().message, error_sz);
|
mjCopyError(error, themodel.model->GetError().message, error_sz);
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
error[0] = 0;
|
error[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
@@ -118,6 +118,30 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// parse XML file in MJCF or URDF format, compile it, return low-level model
|
||||||
|
// if vfs is not NULL, look up files in vfs before reading from disk
|
||||||
|
// error can be NULL; otherwise assumed to have size error_sz
|
||||||
|
mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
|
||||||
|
char* error, int error_sz) {
|
||||||
|
|
||||||
|
if (vfs == nullptr) {
|
||||||
|
return _loadXML(filename, 0, error, error_sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = mj_registerVfsProvider(vfs);
|
||||||
|
if (index < 1) {
|
||||||
|
if (error) {
|
||||||
|
snprintf(error, error_sz, "mj_loadXML: could not register VFS");
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjModel* model = _loadXML(filename, index, error, error_sz);
|
||||||
|
mjp_unregisterResourceProvider(index);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// update XML data structures with info from low-level model, save as MJCF
|
// update XML data structures with info from low-level model, save as MJCF
|
||||||
// returns 1 if successful, 0 otherwise
|
// returns 1 if successful, 0 otherwise
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ target_link_libraries(engine_print_test fixture gmock)
|
|||||||
mujoco_test(engine_ray_test)
|
mujoco_test(engine_ray_test)
|
||||||
target_link_libraries(engine_ray_test fixture gmock)
|
target_link_libraries(engine_ray_test fixture gmock)
|
||||||
|
|
||||||
|
mujoco_test(engine_resource_test)
|
||||||
|
target_link_libraries(engine_resource_test fixture gmock)
|
||||||
|
|
||||||
mujoco_test(engine_sensor_test)
|
mujoco_test(engine_sensor_test)
|
||||||
target_link_libraries(engine_sensor_test fixture gmock)
|
target_link_libraries(engine_sensor_test fixture gmock)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
// Copyright 2023 DeepMind Technologies Limited
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Tests for engine/engine_resource.c
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "src/cc/array_safety.h"
|
||||||
|
#include <mujoco/mjmodel.h>
|
||||||
|
#include <mujoco/mjplugin.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
#include "src/engine/engine_plugin.h"
|
||||||
|
#include "src/engine/engine_resource.h"
|
||||||
|
#include "test/fixture.h"
|
||||||
|
|
||||||
|
namespace mujoco {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::testing::HasSubstr;
|
||||||
|
using ::testing::IsNull;
|
||||||
|
using ::testing::NotNull;
|
||||||
|
using ::testing::StrEq;
|
||||||
|
|
||||||
|
using ResourceTest = MujocoTest;
|
||||||
|
|
||||||
|
int open_nop(mjResource* resource) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int open_str(mjResource* resource) {
|
||||||
|
if (std::strcmp(resource->name, "str://file")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource->data = mju_malloc(100*sizeof(char));
|
||||||
|
std::strcpy((char*) resource->data, "Hello World");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read_nop(mjResource* resource, const void** buffer) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read_str(mjResource* resource, const void** buffer) {
|
||||||
|
*buffer = resource->data;
|
||||||
|
return std::strlen((const char*) resource->data) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_nop(mjResource* resource) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_str(mjResource* resource) {
|
||||||
|
mju_free(resource->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, RegisterProviderSuccess) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "myprefix",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
int count1 = mjp_resourceProviderCount();
|
||||||
|
int i = mjp_registerResourceProvider(&provider);
|
||||||
|
int count2 = mjp_resourceProviderCount();
|
||||||
|
|
||||||
|
EXPECT_GT(i, 0);
|
||||||
|
EXPECT_EQ(count1+1, count2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, RegisterProviderMissingCallbacks) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "myprefix"};
|
||||||
|
|
||||||
|
// install warning handler
|
||||||
|
static char warning[1024];
|
||||||
|
warning[0] = '\0';
|
||||||
|
mju_user_warning = [](const char* msg) {
|
||||||
|
util::strcpy_arr(warning, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
int i = mjp_registerResourceProvider(&provider);
|
||||||
|
|
||||||
|
// warning message related to missing callbacks
|
||||||
|
EXPECT_THAT(warning, HasSubstr("callback"));
|
||||||
|
EXPECT_LT(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, RegisterProviderMissingPrefix) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
// install warning handler
|
||||||
|
static char warning[1024];
|
||||||
|
warning[0] = '\0';
|
||||||
|
mju_user_warning = [](const char* msg) {
|
||||||
|
util::strcpy_arr(warning, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
int i = mjp_registerResourceProvider(&provider);
|
||||||
|
|
||||||
|
// warning message related to missing prefix
|
||||||
|
EXPECT_THAT(warning, HasSubstr("prefix"));
|
||||||
|
EXPECT_LT(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, RegisterProviderSubPrefix) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "prefix",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
mjpResourceProvider provider2 = {.prefix = "pre",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
// install warning handler
|
||||||
|
static char warning[1024];
|
||||||
|
warning[0] = '\0';
|
||||||
|
mju_user_warning = [](const char* msg) {
|
||||||
|
util::strcpy_arr(warning, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, RegisterProviderSuperPrefix) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "prefix",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
mjpResourceProvider provider2 = {.prefix = "prefix2",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
// install warning handler
|
||||||
|
static char warning[1024];
|
||||||
|
warning[0] = '\0';
|
||||||
|
mju_user_warning = [](const char* msg) {
|
||||||
|
util::strcpy_arr(warning, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, RegisterProviderSame) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "prefix",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
mjpResourceProvider provider2 = {.prefix = "prefix",
|
||||||
|
.open = open_nop, .read = read_nop, .close = close_nop};
|
||||||
|
|
||||||
|
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 = {.prefix = "str://",
|
||||||
|
.open = open_str, .read = read_str, .close = close_str};
|
||||||
|
|
||||||
|
// register resource provider
|
||||||
|
int i = mjp_registerResourceProvider(&provider);
|
||||||
|
EXPECT_GT(i, 0);
|
||||||
|
|
||||||
|
// open resource
|
||||||
|
mjResource* resource = mju_openResource("str://file", 0);
|
||||||
|
ASSERT_THAT(resource, NotNull());
|
||||||
|
|
||||||
|
const char* buffer = NULL;
|
||||||
|
int bytes = mju_readResource(resource, (const void**) &buffer);
|
||||||
|
EXPECT_EQ(bytes, std::strlen("Hello World") + 1);
|
||||||
|
EXPECT_THAT(buffer, StrEq("Hello World"));
|
||||||
|
|
||||||
|
mju_closeResource(resource);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResourceTest, GeneralTestFailure) {
|
||||||
|
mjpResourceProvider provider = {.prefix = "str://",
|
||||||
|
.open = open_str, .read = read_str, .close = close_str};
|
||||||
|
|
||||||
|
// 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("str://notfound", 0);
|
||||||
|
ASSERT_THAT(resource, IsNull());
|
||||||
|
|
||||||
|
EXPECT_THAT(warning, HasSubstr("could not open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace mujoco
|
||||||
Reference in New Issue
Block a user