diff --git a/doc/includes/references.h b/doc/includes/references.h index 3a4f910f..35aaa77c 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -655,28 +655,13 @@ struct mjLROpt_ { // options for mj_setLengthRange() mjtNum tolrange; // convergence tolerance (relative to range) }; typedef struct mjLROpt_ mjLROpt; -struct mjVFS_ { // virtual file system for loading from memory - int nfile; // number of files present - char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path - int filesize[mjMAXVFS]; // file size in bytes - void* filedata[mjMAXVFS]; // buffer with file data +struct mjVFS_ { // virtual file system for loading from memory + int nfile; // number of files present + char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path + size_t filesize[mjMAXVFS]; // file size in bytes + void* filedata[mjMAXVFS]; // buffer with file data }; 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); - - // getdir callback from resource provider - void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir); -}; -typedef struct mjResource_ mjResource; struct mjOption_ { // physics options // timing parameters mjtNum timestep; // timestep @@ -1262,15 +1247,21 @@ struct mjModel_ { int* names_map; // internal hash map of names (nnames_map x 1) }; 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 - mjfGetResourceDir getdir; // getdir callback (optional) - void* data; // opaque data pointer (resource invariant) +struct mjResource_ { + char* name; // name of resource (filename, etc) + void* data; // opaque data pointer + const struct mjpResourceProvider* provider; // pointer to the provider }; -typedef struct mjpResourceProvider_ mjpResourceProvider; +typedef struct mjResource_ mjResource; +struct mjpResourceProvider { + const char* prefix; // prefix for match against a resource name + mjfOpenResource open; // opening callback + mjfReadResource read; // reading callback + mjfCloseResource close; // closing callback + mjfGetResourceDir getdir; // get directory callback (optional) + void* data; // opaque data pointer (resource invariant) +}; +typedef struct mjpResourceProvider mjpResourceProvider; typedef enum mjtPluginCapabilityBit_ { mjPLUGIN_ACTUATOR = 1<<0, // actuator forces mjPLUGIN_SENSOR = 1<<1, // sensor measurements diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 61f70100..38f28f44 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -17,6 +17,7 @@ #include + #include // global constants @@ -378,34 +379,14 @@ typedef struct mjLROpt_ mjLROpt; //---------------------------------- mjVFS --------------------------------------------------------- -struct mjVFS_ { // virtual file system for loading from memory - int nfile; // number of files present - char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path - int filesize[mjMAXVFS]; // file size in bytes - void* filedata[mjMAXVFS]; // buffer with file data +struct mjVFS_ { // virtual file system for loading from memory + int nfile; // number of files present + char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path + size_t filesize[mjMAXVFS]; // file size in bytes + void* filedata[mjMAXVFS]; // buffer with file data }; 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); - - // getdir callback from resource provider - void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir); -}; -typedef struct mjResource_ mjResource; - - //---------------------------------- mjOption ------------------------------------------------------ struct mjOption_ { // physics options diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h index a6936e20..aa23bc44 100644 --- a/include/mujoco/mjplugin.h +++ b/include/mujoco/mjplugin.h @@ -22,7 +22,12 @@ //---------------------------------- Resource Provider --------------------------------------------- -#define mjVFS_PREFIX "vfs" // prefix for VFS providers +struct mjResource_ { + char* name; // name of resource (filename, etc) + void* data; // opaque data pointer + const struct mjpResourceProvider* provider; // pointer to the provider +}; +typedef struct mjResource_ mjResource; // callback for opeing a resource, returns zero on failure typedef int (*mjfOpenResource)(mjResource* resource); @@ -39,15 +44,15 @@ typedef void (*mjfCloseResource)(mjResource* resource); typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* ndir); // struct describing a single resource provider -struct mjpResourceProvider_ { - const char* prefix; // prefix for match against a resource name - mjfOpenResource open; // opening callback - mjfReadResource read; // reading callback - mjfCloseResource close; // closing callback - mjfGetResourceDir getdir; // getdir callback (optional) - void* data; // opaque data pointer (resource invariant) +struct mjpResourceProvider { + const char* prefix; // prefix for match against a resource name + mjfOpenResource open; // opening callback + mjfReadResource read; // reading callback + mjfCloseResource close; // closing callback + mjfGetResourceDir getdir; // get directory callback (optional) + void* data; // opaque data pointer (resource invariant) }; -typedef struct mjpResourceProvider_ mjpResourceProvider; +typedef struct mjpResourceProvider mjpResourceProvider; //---------------------------------- Plugins ------------------------------------------------------- diff --git a/introspect/structs.py b/introspect/structs.py index f0cc95d9..5ac4330a 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -106,7 +106,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([ StructFieldDecl( name='filesize', type=ArrayType( - inner_type=ValueType(name='int'), + inner_type=ValueType(name='size_t'), extents=(2000,), ), doc='file size in bytes', diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index 5b13811f..159ab555 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -661,7 +661,7 @@ void mj_saveModel(const mjModel* m, const char* filename, void* buffer, int buff // load model from binary MJB resource -static mjModel* _mj_loadModel(const char* filename, int vfs_provider) { +mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) { int header[NHEADER] = {0}; int expected_header[NHEADER] = {ID, sizeof(mjtNum), getnint(), getnsize(), getnptr()}; int ints[256]; @@ -670,8 +670,11 @@ static mjModel* _mj_loadModel(const char* filename, int vfs_provider) { mjModel *m = 0; mjResource* r = NULL; - if((r = mju_openResource(filename, vfs_provider)) == NULL) { - return NULL; + // first try vfs, otherwise try a provider or OS filesystem + if ((r = mju_openVfsResource(filename, vfs)) == NULL) { + if ((r = mju_openResource(filename)) == NULL) { + return NULL; + } } const void* buffer = NULL; @@ -785,27 +788,6 @@ static mjModel* _mj_loadModel(const char* filename, int vfs_provider) { -// 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) { - mjERROR("could not allocate memory"); - return NULL; - } - - mjModel* model = _mj_loadModel(filename, index); - - mjp_unregisterResourceProvider(index); - return model; -} - - - // de-allocate mjModel void mj_deleteModel(mjModel* m) { if (m) { diff --git a/src/engine/engine_plugin.cc b/src/engine/engine_plugin.cc index 1b314b5d..52e86455 100644 --- a/src/engine/engine_plugin.cc +++ b/src/engine/engine_plugin.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -57,9 +58,6 @@ constexpr int kMaxAttributes = 255; constexpr int kCacheLine = 256; -// vfs prefix -constexpr const char* kVfsPrefix = mjVFS_PREFIX; - // 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 // existing pointers when growing the table. It is expected that for most users, the number of @@ -270,6 +268,7 @@ bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResou p1->open == p2->open && p1->read == p2->read && p1->close == p2->close && + p1->getdir == p2->getdir && p1->data == p2->data); } @@ -532,18 +531,7 @@ void mjp_defaultResourceProvider(mjpResourceProvider* provider) { // globally register a resource provider (thread-safe), return new slot id int mjp_registerResourceProvider(const mjpResourceProvider* provider) { - // check against reserved prefixes - if (PrefixesAreIdentical(kVfsPrefix, provider->prefix)) { - 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) { - // check if prefix is valid URI scheme format + // check if prefix is valid URI scheme format if (!IsValidURISchemeFormat(provider->prefix)) { mju_warning("provider->prefix is '%s' which is not a valid URI scheme format", provider->prefix); @@ -562,35 +550,25 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) { // 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 prefix; - // check if this is a VFS provider - if (PrefixesAreIdentical(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; + 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& global = GetGlobal(); 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* table = &global.table(); - PluginTable* 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) { @@ -600,17 +578,7 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) { } 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 && existing.prefix != nullptr) { + if (existing.prefix != nullptr) { // if identical then return slot number if (PrefixesAreIdentical(provider->prefix, existing.prefix)) { if (ResourceProvidersAreIdentical(provider, &existing)) { @@ -626,7 +594,7 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) { } // allocate a new block of PluginTable if the last allocated block is full - if (free_local_idx == -1 && local_idx == PluginTable::kBlockSize) { + if (local_idx == PluginTable::kBlockSize) { local_idx = 0; table = AddNewTableBlock(table); if (!table) { @@ -636,19 +604,12 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) { // 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; + registered_provider.prefix = prefix.release(); // 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; + global.count().store(count + 1, std::memory_order_release); + return count; }(); // ========= ATTENTION! ========================================================================== @@ -663,46 +624,6 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) { 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& global = GetGlobal(); - auto lock = global.lock_mutex_exclusively(); - int count = global.count().load(std::memory_order_acquire); - - if (slot >= count) { - return; - } - - PluginTable* 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::kBlockSize) { - local_idx -= PluginTable::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().count().load(std::memory_order_acquire); @@ -728,12 +649,6 @@ const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) { 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 (PrefixesAreIdentical(kVfsPrefix, file_prefix.c_str())) { - return nullptr; - } - Global& global = GetGlobal(); auto lock = global.lock_mutex_exclusively(); PluginTable* table = &global.table(); @@ -743,7 +658,6 @@ const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) { for (int i = 0; i < PluginTable::kBlockSize && found_slot < count; ++i, ++found_slot) { - const mjpResourceProvider& provider = table->plugins[i]; const char *prefix = provider.prefix; diff --git a/src/engine/engine_plugin.h b/src/engine/engine_plugin.h index 83fbef83..13068a4a 100644 --- a/src/engine/engine_plugin.h +++ b/src/engine/engine_plugin.h @@ -31,9 +31,6 @@ 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 MJAPI int mjp_pluginCount(); diff --git a/src/engine/engine_resource.c b/src/engine/engine_resource.c index eba0ad62..bd7280fa 100644 --- a/src/engine/engine_resource.c +++ b/src/engine/engine_resource.c @@ -16,40 +16,23 @@ #include #include +#include #include #include -#include #include #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; + uint8_t* buffer; // raw bytes from file + size_t nbuffer; // size of buffer in bytes } file_buffer; -// helper function to fill data from resource provider into provider -static void fillResource(const mjpResourceProvider* provider, mjResource* resource) { - if (provider == NULL) { - resource->read = NULL; - resource->close = NULL; - resource->getdir = NULL; - resource->provider_data = NULL; - } else { - resource->read = provider->read; - resource->close = provider->close; - resource->getdir = provider->getdir; - resource->provider_data = provider->data; - } -} - - // open the given resource; if the name doesn't have a prefix matching with a -// resource provider, then the default_provider is used -// if default_provider non-positive, then the OS filesystem is used -mjResource* mju_openResource(const char* name, int default_provider) { +// resource provider, then the OS filesystem is used +mjResource* mju_openResource(const char* name) { mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource)); const mjpResourceProvider* provider = NULL; if (resource == NULL) { @@ -57,19 +40,22 @@ mjResource* mju_openResource(const char* name, int default_provider) { return NULL; } + // clear out resource + memset(resource, 0, sizeof(mjResource)); + // copy name resource->name = mju_malloc(sizeof(char) * (strlen(name) + 1)); if (resource->name == NULL) { - mju_free(resource); + mju_closeResource(resource); mjERROR("could not allocate memory"); return NULL; } - strcpy(resource->name, name); + memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1)); // find provider based off prefix of name provider = mjp_getResourceProvider(name); if (provider != NULL) { - fillResource(provider, resource); + resource->provider = provider; if (provider->open(resource)) { return resource; } @@ -77,48 +63,19 @@ mjResource* mju_openResource(const char* name, int default_provider) { mju_warning("mju_openResource: could not open resource '%s' " "using a resource provider matching prefix '%s'", name, provider->prefix); - 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 at slot %d", - default_provider); - mju_free(resource->name); - mju_free(resource); - return NULL; - } - - fillResource(provider, resource); - if (provider->open(resource)) { - return resource; - } - - mju_warning("mju_openResource: could not open resource '%s' " - "with default provider at slot %d", - name, default_provider); - mju_free(resource->name); - mju_free(resource); + mju_closeResource(resource); return NULL; } // lastly fallback to OS filesystem - else { - fillResource(NULL, resource); - 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; - } + resource->provider = 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_closeResource(resource); + return NULL; } return resource; } @@ -131,20 +88,20 @@ void mju_closeResource(mjResource* resource) { return; } - // use the resource provider to close resource - if (resource->close) { - resource->close(resource); - } - - // if provider is NULL, then OS filesystem is used - else { + // use the resource provider close callback + if (resource->provider && resource->provider->close) { + resource->provider->close(resource); + } else { + // clear OS filesystem if present file_buffer* fb = (file_buffer*) resource->data; - mju_free(fb->buffer); - mju_free(fb); + if (fb) { + if (fb->buffer) mju_free(fb->buffer); + mju_free(fb); + } } - // free name and resource - mju_free(resource->name); + // free resource + if (resource->name) mju_free(resource->name); mju_free(resource); } @@ -157,12 +114,12 @@ int mju_readResource(mjResource* resource, const void** buffer) { return 0; } - if (resource->read) { - return resource->read(resource, buffer); + if (resource->provider) { + return resource->provider->read(resource, buffer); } - // if provider read callback is NULL, then OS filesystem is used + // if provider is NULL, then OS filesystem is used const file_buffer* fb = (file_buffer*) resource->data; *buffer = fb->buffer; return fb->nbuffer; @@ -175,14 +132,14 @@ void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) { *dir = NULL; *ndir = 0; - if (!resource) { + if (resource == NULL) { return; } // provider is not OS filesystem - if (resource->read) { - if (resource->getdir) { - resource->getdir(resource, dir, ndir); + if (resource->provider) { + if (resource->provider->getdir) { + resource->provider->getdir(resource, dir, ndir); } } else { *dir = resource->name; @@ -211,7 +168,7 @@ int mju_dirnamelen(const char* path) { // read file into memory buffer (allocated here with mju_malloc) -void* mju_fileToMemory(const char* filename, int* filesize) { +void* mju_fileToMemory(const char* filename, size_t* filesize) { // open file *filesize = 0; FILE* fp = fopen(filename, "rb"); diff --git a/src/engine/engine_resource.h b/src/engine/engine_resource.h index 7c659d0b..b45b3a82 100644 --- a/src/engine/engine_resource.h +++ b/src/engine/engine_resource.h @@ -15,6 +15,8 @@ #ifndef MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_ #define MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_ +#include + #include #include "engine/engine_plugin.h" @@ -23,9 +25,8 @@ extern "C" { #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); +// resource provider, then the OS filesystem is used +MJAPI mjResource* mju_openResource(const char* name); // close the given resource; no-op if resource is NULL MJAPI void mju_closeResource(mjResource* resource); @@ -37,11 +38,15 @@ MJAPI int mju_readResource(mjResource* resource, const void** buffer); // sets for a resource with a name partitioned as {dir}{filename}, the dir and ndir pointers MJAPI void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir); +// Returns > 0 if resource has been modified since last read, 0 if not, and < 0 +// if inconclusive +MJAPI int mju_isModifiedResource(const mjResource* resource); + // get the length of the dirname portion of a given path int mju_dirnamelen(const char* path); // read file into memory buffer (allocated here with mju_malloc) -void* mju_fileToMemory(const char* filename, int* filesize); +void* mju_fileToMemory(const char* filename, size_t* filesize); #ifdef __cplusplus } diff --git a/src/engine/engine_vfs.c b/src/engine/engine_vfs.c index e4246bb7..0535111e 100644 --- a/src/engine/engine_vfs.c +++ b/src/engine/engine_vfs.c @@ -14,10 +14,10 @@ #include "engine/engine_vfs.h" +#include #include #include "engine/engine_array_safety.h" -#include "engine/engine_plugin.h" #include "engine/engine_resource.h" #include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" @@ -90,7 +90,7 @@ int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename) { mjSTRNCPY(vfs->filename[vfs->nfile], newname); // allocate and read - int filesize = 0; + size_t filesize = 0; vfs->filedata[vfs->nfile] = mju_fileToMemory(fullname, &filesize); if (!vfs->filedata[vfs->nfile]) { return -1; @@ -211,11 +211,11 @@ void mj_deleteVFS(mjVFS* vfs) { // open callback for the VFS resource provider static int vfs_open_callback(mjResource* resource) { - if (!resource || !resource->provider_data || !resource->name) { + if (!resource || !resource->name || !resource->data) { return 0; } - const mjVFS* vfs = (const mjVFS*) resource->provider_data; + const mjVFS* vfs = (const mjVFS*) resource->data; return mj_findFileVFS(vfs, resource->name) >= 0; } @@ -223,12 +223,12 @@ static int vfs_open_callback(mjResource* resource) { // read callback for the VFS resource provider static int vfs_read_callback(mjResource* resource, const void** buffer) { - if (!resource || !resource->provider_data) { + if (!resource || !resource->name || !resource->data) { *buffer = NULL; return -1; } - const mjVFS* vfs = (const mjVFS*) resource->provider_data; + const mjVFS* vfs = (const mjVFS*) resource->data; int i = mj_findFileVFS(vfs, resource->name); if (i < 0) { *buffer = NULL; @@ -260,16 +260,49 @@ static void vfs_getdir_callback(mjResource* resource, const char** dir, int* ndi -// 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, - .getdir = &vfs_getdir_callback, - .data = (void*) vfs +// open VFS resource +mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs) { + if (vfs == NULL) { + return NULL; + } + + // VFS provider + static struct mjpResourceProvider provider = { + .prefix = NULL, + .data = NULL, + .open = &vfs_open_callback, + .read = &vfs_read_callback, + .close = &vfs_close_callback, + .getdir = &vfs_getdir_callback, }; - return mjp_registerResourceProviderInternal(&provider); + // create resource + mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource)); + if (resource == NULL) { + mjERROR("could not allocate memory"); + return NULL; + } + + // clear out resource + memset(resource, 0, sizeof(mjResource)); + + // copy name + resource->name = mju_malloc(sizeof(char) * (strlen(name) + 1)); + if (resource->name == NULL) { + mju_closeResource(resource); + mjERROR("could not allocate memory"); + return NULL; + } + memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1)); + resource->data = (void*) vfs; + + // open resource + resource->provider = &provider; + if (provider.open(resource)) { + return resource; + } + + // not found in VFS + mju_closeResource(resource); + return NULL; } diff --git a/src/engine/engine_vfs.h b/src/engine/engine_vfs.h index 7fa0cd40..b9b578a1 100644 --- a/src/engine/engine_vfs.h +++ b/src/engine/engine_vfs.h @@ -17,6 +17,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,8 +41,8 @@ MJAPI int mj_deleteFileVFS(mjVFS* vfs, const char* filename); // delete all files from 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); +// open VFS resource +MJAPI mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs); #ifdef __cplusplus } diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 6a04f4b3..548819ab 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -29,6 +29,7 @@ #include #include +#include #include "cc/array_safety.h" #include "engine/engine_crossplatform.h" #include "engine/engine_resource.h" @@ -329,7 +330,7 @@ void mjCMesh::LoadSDF() { // compiler -void mjCMesh::Compile(int vfs_provider) { +void mjCMesh::Compile(const mjVFS* vfs) { // load file if (!file_.empty()) { // remove path from file if necessary @@ -348,7 +349,7 @@ void mjCMesh::Compile(int vfs_provider) { } string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file_); - mjResource* resource = LoadResource(filename, vfs_provider); + mjResource* resource = LoadResource(filename, vfs); try { if (asset_type == "model/stl") { @@ -1836,7 +1837,7 @@ mjCSkin::~mjCSkin() { // compiler -void mjCSkin::Compile(int vfs_provider) { +void mjCSkin::Compile(const mjVFS* vfs) { // load file if (!file.empty()) { @@ -1865,7 +1866,7 @@ void mjCSkin::Compile(int vfs_provider) { } string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); - mjResource* resource = LoadResource(filename, vfs_provider); + mjResource* resource = LoadResource(filename, vfs); try { LoadSKN(resource); diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 3bdde0ed..8b782f08 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -2423,7 +2423,7 @@ static void warninghandler(const char* msg) { // compiler -mjModel* mjCModel::Compile(int vfs_provider) { +mjModel* mjCModel::Compile(const mjVFS* vfs) { // The volatile keyword is necessary to prevent a possible memory leak due to // an interaction between longjmp and compiler optimization. Specifically, at // the point where the setjmp takes places, these pointers have never been @@ -2454,7 +2454,7 @@ mjModel* mjCModel::Compile(int vfs_provider) { // TryCompile resulted in an mju_error which was converted to a longjmp. throw mjCError(0, "engine error: %s", errortext); } - TryCompile(*const_cast(&m), *const_cast(&data), vfs_provider); + TryCompile(*const_cast(&m), *const_cast(&data), vfs); } catch (mjCError err) { // deallocate everything allocated in Compile mj_deleteModel(m); @@ -2480,7 +2480,7 @@ mjModel* mjCModel::Compile(int vfs_provider) { } -void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) { +void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { // check if nan test works double test = mjNAN; if (mjuu_defined(test)) { @@ -2557,7 +2557,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) { // compile meshes (needed for geom compilation) for (int i=0; iCompile(vfs_provider); + meshes[i]->Compile(vfs); } // automatically set nuser fields @@ -2616,9 +2616,9 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) { } // compile all other objects except for keyframes - for (int i=0; iCompile(vfs_provider); - for (int i=0; iCompile(vfs_provider); - for (int i=0; iCompile(vfs_provider); + for (int i=0; iCompile(vfs); + for (int i=0; iCompile(vfs); + for (int i=0; iCompile(vfs); for (int i=0; iCompile(); for (int i=0; iCompile(); for (int i=0; iCompile(); diff --git a/src/user/user_model.h b/src/user/user_model.h index 4ac38f3a..f4a3ed68 100644 --- a/src/user/user_model.h +++ b/src/user/user_model.h @@ -65,7 +65,7 @@ class mjCModel { mjCModel(); // constructor ~mjCModel(); // destructor - mjModel* Compile(int vfs_provider = 0); // COMPILER: construct mjModel + mjModel* Compile(const mjVFS* vfs = 0); // COMPILER: construct mjModel bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back void FuseStatic(void); // fuse static bodies with parent void FuseReindex(mjCBody* body); // reindex elements during fuse @@ -163,8 +163,8 @@ class mjCModel { int nuser_sensor; // number of mjtNums in sensor_user private: - void TryCompile(mjModel*& m, mjData*& d, int vfs_provider); - mjModel* _Compile(int vfs_provider); + void TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs); + mjModel* _Compile(const mjVFS* vfs); void Clear(void); // clear objects allocated by Compile diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 853d5a70..75eca814 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -39,6 +39,7 @@ #include "engine/engine_util_misc.h" #include "engine/engine_util_solve.h" #include "engine/engine_util_spatial.h" +#include "engine/engine_vfs.h" #include "user/user_model.h" #include "user/user_util.h" @@ -495,17 +496,14 @@ mjCBase::mjCBase() { // load resource if found (fallback to OS filesystem) -mjResource* mjCBase::LoadResource(string filename, int provider) { +mjResource* mjCBase::LoadResource(string filename, const mjVFS* vfs) { mjResource* r = nullptr; const char* cname = filename.c_str(); - // try reading from given provider - if ((r = mju_openResource(cname, provider)) == nullptr) { - if (!provider) { - throw mjCError(0, "file not found: '%s'", cname); - } - // if provider wasn't the OS filesystem try to fallback to OS filesystem - if ((r = mju_openResource(filename.c_str(), 0)) == nullptr) { + // try reading from provided VFS + if ((r = mju_openVfsResource(cname, vfs)) == nullptr) { + // not in vfs try a provider or fallback to OS filesystem + if ((r = mju_openResource(filename.c_str())) == nullptr) { throw mjCError(this, "resource not found via provider or OS filesystem: '%s'", cname); } } @@ -2194,7 +2192,7 @@ void mjCHField::LoadPNG(mjResource* resource) { // compiler -void mjCHField::Compile(int vfs_provider) { +void mjCHField::Compile(const mjVFS* vfs) { // check size parameters for (int i=0; i<4; i++) if (size[i]<=0) @@ -2226,7 +2224,7 @@ void mjCHField::Compile(int vfs_provider) { } string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); - mjResource* resource = LoadResource(filename, vfs_provider); + mjResource* resource = LoadResource(filename, vfs); try { if (asset_type == "image/png") { @@ -2617,7 +2615,7 @@ void mjCTexture::LoadCustom(mjResource* resource, // load from PNG or custom file, flip if specified -void mjCTexture::LoadFlip(string filename, int vfs_provider, +void mjCTexture::LoadFlip(string filename, const mjVFS* vfs, std::vector& image, unsigned int& w, unsigned int& h) { std::string asset_type = GetAssetContentType(filename, content_type); @@ -2631,7 +2629,7 @@ void mjCTexture::LoadFlip(string filename, int vfs_provider, throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - mjResource* resource = LoadResource(filename, vfs_provider); + mjResource* resource = LoadResource(filename, vfs); try { if (asset_type == "image/png") { @@ -2693,11 +2691,11 @@ void mjCTexture::LoadFlip(string filename, int vfs_provider, // load 2D -void mjCTexture::Load2D(string filename, int vfs_provider) { +void mjCTexture::Load2D(string filename, const mjVFS* vfs) { // load PNG or custom unsigned int w, h; std::vector image; - LoadFlip(filename, vfs_provider, image, w, h); + LoadFlip(filename, vfs, image, w, h); // assign size width = w; @@ -2716,7 +2714,7 @@ void mjCTexture::Load2D(string filename, int vfs_provider) { // load cube or skybox from single file (repeated or grid) -void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) { +void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { // check gridsize if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) { throw mjCError(this, @@ -2727,7 +2725,7 @@ void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) { // load PNG or custom unsigned int w, h; std::vector image; - LoadFlip(filename, vfs_provider, image, w, h); + LoadFlip(filename, vfs, image, w, h); // check gridsize for compatibility if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) { @@ -2816,7 +2814,7 @@ void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) { // load cube or skybox from separate file -void mjCTexture::LoadCubeSeparate(int vfs_provider) { +void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { // keep track of which faces were defined int loaded[6] = {0, 0, 0, 0, 0, 0}; @@ -2834,7 +2832,7 @@ void mjCTexture::LoadCubeSeparate(int vfs_provider) { // load PNG or custom unsigned int w, h; std::vector image; - LoadFlip(filename, vfs_provider, image, w, h); + LoadFlip(filename, vfs, image, w, h); // PNG must be square if (w!=h) { @@ -2888,7 +2886,7 @@ void mjCTexture::LoadCubeSeparate(int vfs_provider) { // compiler -void mjCTexture::Compile(int vfs_provider) { +void mjCTexture::Compile(const mjVFS* vfs) { // builtin if (builtin!=mjBUILTIN_NONE) { // check size @@ -2931,9 +2929,9 @@ void mjCTexture::Compile(int vfs_provider) { // dispatch if (type==mjTEXTURE_2D) { - Load2D(filename, vfs_provider); + Load2D(filename, vfs); } else { - LoadCubeSingle(filename, vfs_provider); + LoadCubeSingle(filename, vfs); } } @@ -2961,7 +2959,7 @@ void mjCTexture::Compile(int vfs_provider) { } // only cube and skybox - LoadCubeSeparate(vfs_provider); + LoadCubeSeparate(vfs); } // make sure someone allocated data; SHOULD NOT OCCUR diff --git a/src/user/user_objects.h b/src/user/user_objects.h index e5bbd58a..148b3997 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -23,6 +23,7 @@ #include "lodepng.h" #include +#include // forward declarations of all mjC/X classes class mjCError; @@ -173,7 +174,7 @@ class mjCBase { public: // load resource if found (fallback to OS filesystem) - mjResource* LoadResource(std::string filename, int provider); + mjResource* LoadResource(std::string filename, const mjVFS* vfs); // Get and sanitize content type from raw_text if not empty, otherwise parse // content type from resource_name; throw on failure @@ -572,7 +573,7 @@ class mjCMesh: public mjCBase { void set_usertexcoord(std::optional>&& usertexcoord); void set_userface(std::optional>&& userface); - void Compile(int vfs_provider); // compiler + void Compile(const mjVFS* vfs); // compiler double* GetPosPtr(mjtMeshType type); // get position double* GetQuatPtr(mjtMeshType type); // get orientation double* GetInertiaBoxPtr(mjtMeshType type); // get inertia box @@ -696,7 +697,7 @@ class mjCSkin: public mjCBase { private: mjCSkin(mjCModel* = 0); // constructor ~mjCSkin(); // destructor - void Compile(int vfs_provider); // compiler + void Compile(const mjVFS* vfs); // compiler void LoadSKN(mjResource* resource); // load skin in SKN BIN format int matid; // material id @@ -723,7 +724,7 @@ class mjCHField : public mjCBase { private: mjCHField(mjCModel* model); // constructor ~mjCHField(); // destructor - void Compile(int vfs_provider); // compiler + void Compile(const mjVFS* vfs); // compiler void LoadCustom(mjResource* resource); // load from custom format void LoadPNG(mjResource* resource); // load from PNG format @@ -768,15 +769,15 @@ class mjCTexture : public mjCBase { private: mjCTexture(mjCModel*); // constructor ~mjCTexture(); // destructior - void Compile(int vfs_provider); // compiler + void Compile(const mjVFS* vfs); // compiler void Builtin2D(void); // make builtin 2D void BuiltinCube(void); // make builtin cube - void Load2D(std::string filename, int vfs_provider); // load 2D from file - void LoadCubeSingle(std::string filename, int vfs_provider); // load cube from single file - void LoadCubeSeparate(int vfs_provider); // load cube from separate files + void Load2D(std::string filename, const mjVFS* vfs); // load 2D from file + void LoadCubeSingle(std::string filename, const mjVFS* vfs); // load cube from single file + void LoadCubeSeparate(const mjVFS* vfs); // load cube from separate files - void LoadFlip(std::string filename, int vfs_provider, // load and flip + void LoadFlip(std::string filename, const mjVFS* vfs, // load and flip std::vector& image, unsigned int& w, unsigned int& h); diff --git a/src/xml/xml.cc b/src/xml/xml.cc index 7b8ec3cd..fda3cde8 100644 --- a/src/xml/xml.cc +++ b/src/xml/xml.cc @@ -22,9 +22,11 @@ #include +#include #include "cc/array_safety.h" #include "engine/engine_crossplatform.h" #include "engine/engine_resource.h" +#include "engine/engine_vfs.h" #include "user/user_model.h" #include "user/user_util.h" #include "xml/xml_native_reader.h" @@ -110,7 +112,7 @@ string mjWriteXML(mjCModel* model, char* error, int error_sz) { // find include elements recursively, replace them with subtree from xml file static XMLElement* mjIncludeXML(XMLElement* elem, string dir, - int vfs_provider, vector& included) { + const mjVFS* vfs, vector& included) { // include element: process if (!strcasecmp(elem->Value(), "include")) { // make sure include has no children @@ -133,9 +135,9 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir, // get data source mjResource *resource = nullptr; const char* xmlstring = nullptr; - if ((resource = mju_openResource(filename.c_str(), vfs_provider)) == nullptr) { - // load from OS filesystem - if (!vfs_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) { + if ((resource = mju_openVfsResource(filename.c_str(), vfs)) == nullptr) { + // load from provider or OS filesystem + if ((resource = mju_openResource(filename.c_str())) == nullptr) { throw mjXError(elem, "Could not open file '%s'", filename.c_str()); } } @@ -196,14 +198,14 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir, } // run XMLInclude on first new child - return mjIncludeXML(first->ToElement(), dir, vfs_provider, included); + return mjIncludeXML(first->ToElement(), dir, vfs, included); } // otherwise check all child elements, return self else { XMLElement* child = elem->FirstChildElement(); while (child) { - child = mjIncludeXML(child, dir, vfs_provider, included); + child = mjIncludeXML(child, dir, vfs, included); if (child) { child = child->NextSiblingElement(); } @@ -215,7 +217,7 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir, // Main parser function -mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int error_sz) { +mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz) { LocaleOverride locale_override; // check arguments @@ -235,9 +237,9 @@ mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int er // get data source mjResource* resource = nullptr; const char* xmlstring = nullptr; - if ((resource = mju_openResource(filename, vfs_provider)) == nullptr) { - // load from OS filesystem - if (!vfs_provider || (resource = mju_openResource(filename, 0)) == nullptr) { + if ((resource = mju_openVfsResource(filename, vfs)) == nullptr) { + // load from provider or fallback to OS filesystem + if ((resource = mju_openResource(filename)) == nullptr) { if (error) { snprintf(error, error_sz, "mjParseXML: could not open file '%s'", filename); } @@ -303,7 +305,7 @@ mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int er // find include elements, replace them with subtree from xml file vector included; included.push_back(filename); - mjIncludeXML(root, model->modelfiledir, vfs_provider, included); + mjIncludeXML(root, model->modelfiledir, vfs, included); // parse MuJoCo model mjXReader parser; diff --git a/src/xml/xml.h b/src/xml/xml.h index acbe81ce..84ef9ae3 100644 --- a/src/xml/xml.h +++ b/src/xml/xml.h @@ -26,7 +26,7 @@ std::string mjWriteXML(mjCModel* model, char* error, int error_sz); // Main parser function -mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int error_sz); +mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz); #endif // MUJOCO_SRC_XML_XML_H_ diff --git a/src/xml/xml_api.cc b/src/xml/xml_api.cc index bad07392..4aa468eb 100644 --- a/src/xml/xml_api.cc +++ b/src/xml/xml_api.cc @@ -70,20 +70,23 @@ static std::mutex themutex; //---------------------------------- Functions ----------------------------------------------------- -// mj_loadXML helper function -mjModel* _loadXML(const char* filename, int vfs_provider, - char* error, int error_sz) { +// 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) { + // serialize access to themodel std::lock_guard lock(themutex); // parse new model - mjCModel* newmodel = mjParseXML(filename, vfs_provider, error, error_sz); + mjCModel* newmodel = mjParseXML(filename, vfs, error, error_sz); if (!newmodel) { return nullptr; } // compile new model - mjModel* m = newmodel->Compile(vfs_provider); + mjModel* m = newmodel->Compile(vfs); if (!m) { mjCopyError(error, newmodel->GetError().message, error_sz); delete newmodel; @@ -106,31 +109,6 @@ mjModel* _loadXML(const char* filename, int vfs_provider, -// 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 // returns 1 if successful, 0 otherwise // error can be NULL; otherwise assumed to have size error_sz diff --git a/test/engine/engine_resource_test.cc b/test/engine/engine_resource_test.cc index 686c297d..c3287375 100644 --- a/test/engine/engine_resource_test.cc +++ b/test/engine/engine_resource_test.cc @@ -19,12 +19,10 @@ #include #include #include "src/cc/array_safety.h" -#include #include #include #include "src/engine/engine_plugin.h" #include "src/engine/engine_resource.h" -#include "src/engine/engine_vfs.h" #include "test/fixture.h" namespace mujoco { @@ -68,20 +66,48 @@ void close_str(mjResource* resource) { } TEST_F(ResourceTest, RegisterProviderSuccess) { - mjpResourceProvider provider = {"my-prefix.123+45", open_nop, read_nop, close_nop, - nullptr}; + mjpResourceProvider provider = { + "my-prefix.123+45", open_nop, read_nop, close_nop + }; int count1 = mjp_resourceProviderCount(); int i = mjp_registerResourceProvider(&provider); int count2 = mjp_resourceProviderCount(); EXPECT_GT(i, 0); - EXPECT_EQ(count1+1, count2); + EXPECT_EQ(count2 - count1, 1); +} + +TEST_F(ResourceTest, RegisterProviderMultipleSuccess) { + mjpResourceProvider provider = { + "my-prefix.123+44", open_nop, read_nop, close_nop + }; + + mjpResourceProvider provider2 = { + "my-prefix.123+46", open_nop, read_nop, close_nop + }; + + + mjpResourceProvider provider3 = { + "my-prefix.123+41", open_nop, read_nop, close_nop + }; + + int count1 = mjp_resourceProviderCount(); + int i = mjp_registerResourceProvider(&provider); + int i2 = mjp_registerResourceProvider(&provider2); + int i3 = mjp_registerResourceProvider(&provider3); + int count2 = mjp_resourceProviderCount(); + + EXPECT_GT(i, 0); + EXPECT_GT(i2, 0); + EXPECT_GT(i3, 0); + EXPECT_EQ(count2 - count1, 3); } TEST_F(ResourceTest, RegisterProviderMissingCallbacks) { - mjpResourceProvider provider = {"myprefix", nullptr, nullptr, nullptr, - nullptr}; + mjpResourceProvider provider = { + "myprefix" + }; // install warning handler static char warning[1024]; @@ -98,7 +124,9 @@ TEST_F(ResourceTest, RegisterProviderMissingCallbacks) { } TEST_F(ResourceTest, RegisterProviderMissingPrefix) { - mjpResourceProvider provider = {"", open_nop, read_nop, close_nop, nullptr, nullptr}; + mjpResourceProvider provider = { + "", open_nop, read_nop, close_nop + }; // install warning handler static char warning[1024]; @@ -115,7 +143,9 @@ TEST_F(ResourceTest, RegisterProviderMissingPrefix) { } TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) { - mjpResourceProvider provider = {"1invalid", open_nop, read_nop, close_nop, nullptr, nullptr}; + mjpResourceProvider provider = { + "1invalid", open_nop, read_nop, close_nop + }; // install warning handler static char warning[1024]; @@ -132,7 +162,9 @@ TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) { } TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) { - mjpResourceProvider provider = {"invalid:", open_nop, read_nop, close_nop, nullptr, nullptr}; + mjpResourceProvider provider = { + "invalid:", open_nop, read_nop, close_nop + }; // install warning handler static char warning[1024]; @@ -149,11 +181,13 @@ TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) { } TEST_F(ResourceTest, RegisterProviderSame) { - mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider = { + "prefix", open_nop, read_nop, close_nop + }; - mjpResourceProvider provider2 = {"prefix", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider2 = { + "prefix", open_nop, read_nop, close_nop + }; int i1 = mjp_registerResourceProvider(&provider); int count1 = mjp_resourceProviderCount(); @@ -166,11 +200,13 @@ TEST_F(ResourceTest, RegisterProviderSame) { } TEST_F(ResourceTest, RegisterProviderSameCase) { - mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider = { + "prefix", open_nop, read_nop, close_nop + }; - mjpResourceProvider provider2 = {"PREFIX", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider2 = { + "PREFIX", open_nop, read_nop, close_nop + }; int i1 = mjp_registerResourceProvider(&provider); int count1 = mjp_resourceProviderCount(); @@ -183,15 +219,16 @@ TEST_F(ResourceTest, RegisterProviderSameCase) { } TEST_F(ResourceTest, GeneralTest) { - mjpResourceProvider provider = {"str", open_str, read_str, close_str, - nullptr, nullptr}; + mjpResourceProvider provider = { + "str", open_str, read_str, close_str + }; // register resource provider int i = mjp_registerResourceProvider(&provider); EXPECT_GT(i, 0); // open resource - mjResource* resource = mju_openResource("str:file", 0); + mjResource* resource = mju_openResource("str:file"); ASSERT_THAT(resource, NotNull()); const char* buffer = NULL; @@ -203,8 +240,9 @@ TEST_F(ResourceTest, GeneralTest) { } TEST_F(ResourceTest, GeneralTestFailure) { - mjpResourceProvider provider = {"str", open_str, read_str, close_str, - nullptr, nullptr}; + mjpResourceProvider provider = { + "str", open_str, read_str, close_str + }; // register resource provider int i = mjp_registerResourceProvider(&provider); @@ -219,15 +257,16 @@ TEST_F(ResourceTest, GeneralTestFailure) { }; // open resource - mjResource* resource = mju_openResource("str:notfound", 0); + mjResource* resource = mju_openResource("str:notfound"); ASSERT_THAT(resource, IsNull()); EXPECT_THAT(warning, HasSubstr("could not open")); } TEST_F(ResourceTest, NameWithValidPrefix) { - mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider = { + "nop", open_nop, read_nop, close_nop + }; // register resource provider int i = mjp_registerResourceProvider(&provider); @@ -242,14 +281,15 @@ TEST_F(ResourceTest, NameWithValidPrefix) { }; // open resource - mjResource* resource = mju_openResource("nop:found", 0); + mjResource* resource = mju_openResource("nop:found"); ASSERT_THAT(resource, NotNull()); mju_closeResource(resource); } TEST_F(ResourceTest, NameWithUpperCasePrefix) { - mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider = { + "nop", open_nop, read_nop, close_nop, + }; // register resource provider int i = mjp_registerResourceProvider(&provider); @@ -264,14 +304,15 @@ TEST_F(ResourceTest, NameWithUpperCasePrefix) { }; // open resource - mjResource* resource = mju_openResource("NOP:found", 0); + mjResource* resource = mju_openResource("NOP:found"); ASSERT_THAT(resource, NotNull()); mju_closeResource(resource); } TEST_F(ResourceTest, NameWithInvalidPrefix) { - mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop, - nullptr, nullptr}; + mjpResourceProvider provider = { + "nop", open_nop, read_nop, close_nop + }; // register resource provider int i = mjp_registerResourceProvider(&provider); @@ -286,33 +327,9 @@ TEST_F(ResourceTest, NameWithInvalidPrefix) { }; // open resource - mjResource* resource = mju_openResource("nopfound", 0); + mjResource* resource = mju_openResource("nopfound"); ASSERT_THAT(resource, IsNull()); } -TEST_F(ResourceTest, VFSProvider) { - // load VFS on the heap - auto vfs = std::make_unique(); - mj_defaultVFS(vfs.get()); - mj_makeEmptyFileVFS(vfs.get(), "file", 1); - - // register resource provider - int i = mj_registerVfsProvider(vfs.get()); - EXPECT_GT(i, 0); - - - // install warning handler - static char warning[1024]; - warning[0] = '\0'; - mju_user_warning = [](const char* msg) { - util::strcpy_arr(warning, msg); - }; - - // open resource - mjResource* resource = mju_openResource("vfs:file", 0); - ASSERT_THAT(resource, IsNull()); - mj_deleteFileVFS(vfs.get(), "file"); -} - } // namespace } // namespace mujoco diff --git a/test/fixture.cc b/test/fixture.cc index 10432236..4c451b65 100644 --- a/test/fixture.cc +++ b/test/fixture.cc @@ -14,6 +14,7 @@ #include "test/fixture.h" +#include #include #include #include @@ -79,10 +80,11 @@ mjModel* LoadModelFromString(std::string_view xml, char* error, resource->data = &(resource->name[strlen("LoadModelFromString:")]); return 1; }; - resourceProvider.read = +[](mjResource* resource, const void** buffer) { - *buffer = resource->data; - return (int) strlen((const char*) resource->data); - }; + resourceProvider.read = + +[](mjResource* resource, const void** buffer) { + *buffer = resource->data; + return (int) strlen((const char*) resource->data); + }; resourceProvider.close = +[](mjResource* resource) {}; mjp_registerResourceProvider(&resourceProvider); } diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index b4f9f6a1..7b318b97 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -1756,7 +1756,7 @@ public unsafe struct _mjVFS { public int nfile; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000 * 1000)] public char[] filename; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public int[] filesize; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public UIntPtr[] filesize; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public IntPtr[] filedata; }