From a4a621f6e3309c881212ec92ec8539862de3bc42 Mon Sep 17 00:00:00 2001 From: Kyle Bayes Date: Wed, 21 Feb 2024 02:24:35 -0800 Subject: [PATCH] Add checksums (used as a timestamp) to VFS. Deprecate mj_makeEmptyFileVFS in favor of mj_addBufferVFS. PiperOrigin-RevId: 608915388 Change-Id: Id9e9b042799bc1bf547f0795ae6f386447d53cdd --- doc/APIreference/functions.rst | 19 ++++-- doc/changelog.rst | 15 +++-- doc/includes/references.h | 15 +++-- include/mujoco/mjmodel.h | 12 ++-- include/mujoco/mjplugin.h | 13 ++-- include/mujoco/mujoco.h | 6 +- introspect/functions.py | 40 ++++++++++-- introspect/structs.py | 8 +++ src/engine/engine_resource.c | 18 +++--- src/engine/engine_resource.h | 9 +-- src/engine/engine_vfs.c | 92 +++++++++++++++++++++++++++- src/engine/engine_vfs.h | 7 ++- test/engine/engine_util_misc_test.cc | 1 - test/engine/engine_vfs_test.cc | 68 ++++++++++++-------- unity/Runtime/Bindings/MjBindings.cs | 6 +- 15 files changed, 251 insertions(+), 78 deletions(-) diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 0abccf7a..097e675e 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -1109,14 +1109,14 @@ mj_addFileVFS Add file to VFS. The directory argument is optional and can be NULL or empty. Returns 0 on success, 1 when VFS is full, 2 on name collision, or -1 when an internal error occurs. -.. _mj_makeEmptyFileVFS: +.. _mj_addBufferVFS: -mj_makeEmptyFileVFS -~~~~~~~~~~~~~~~~~~~ +mj_addBufferVFS +~~~~~~~~~~~~~~~ -.. mujoco-include:: mj_makeEmptyFileVFS +.. mujoco-include:: mj_addBufferVFS -Make empty file in VFS, return 0: success, 1: full, 2: repeated name. +Add file to VFS from buffer, return 0: success, 1: full, 2: repeated name, -1: failed to load. .. _mj_findFileVFS: @@ -1145,6 +1145,15 @@ mj_deleteVFS Delete all files from VFS. +.. _mj_makeEmptyFileVFS: + +mj_makeEmptyFileVFS +~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mj_makeEmptyFileVFS + +deprecated: use mj_copyBufferVFS. + .. _Initialization: Initialization diff --git a/doc/changelog.rst b/doc/changelog.rst index 861cfc61..30a419e3 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,20 +12,25 @@ General :at:`ctrlrange` or :at:`actrange` (respectively), according to the range of the transmission target (joint or tendon). See :ref:`position/inheritrange` for details. +2. Deprecated :ref:`mj_makeEmptyFileVFS` in favor of :ref:`mj_addBufferVFS`. :ref:`mjVFS` now computes checksums of + its internal file buffers. :ref:`mj_addBufferVFS` allocates an empty buffer with a given name in an mjVFS and + copies the data buffer into it, combining and replacing the deprecated two-step process of calling + :ref:`mj_makeEmptyFileVFS` followed by a direct copy into the given mjVFS internal file buffer. MJX ^^^ -2. Improved performance of getting and putting device data. + +3. Improved performance of getting and putting device data. - Use ``tobytes()`` for numpy array serialization, which is orders of magnitude faster than converting to tuples. - Avoid reallocating host ``mjData`` arrays when array shapes are unchanged. - Speed up calculation of ``mjx.ncon`` for models with many geoms. - Avoid calling ``mjx.ncon`` in ``mjx.get_data_into`` when ``nc`` can be derived from ``mjx.Data``. -3. Fixed a bug in ``mjx-viewer`` that prevented it from running. Updated ``mjx-viewer`` to use newer +4. Fixed a bug in ``mjx-viewer`` that prevented it from running. Updated ``mjx-viewer`` to use newer ``mjx.get_data_into`` function call. -4. Fixed a bug in ``mjx.euler`` that applied incorrect damping when using dense mass matrices. -5. Fixed a bug in ``mjx.solve`` that was causing slow convergence when using ``mjSOL_NEWTON`` in :ref:`mjtSolver`. -6. Added support for :ref:`mjOption.impratio` to ``mjx.Model``. +5. Fixed a bug in ``mjx.euler`` that applied incorrect damping when using dense mass matrices. +6. Fixed a bug in ``mjx.solve`` that was causing slow convergence when using ``mjSOL_NEWTON`` in :ref:`mjtSolver`. +7. Added support for :ref:`mjOption.impratio` to ``mjx.Model``. Python bindings ^^^^^^^^^^^^^^^ diff --git a/doc/includes/references.h b/doc/includes/references.h index 913d2465..a61663d1 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -691,11 +691,12 @@ 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 - size_t 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 + uint64_t filestamp[mjMAXVFS]; // checksum of the file data }; typedef struct mjVFS_ mjVFS; struct mjOption_ { // physics options @@ -1375,6 +1376,7 @@ typedef struct mjModel_ mjModel; struct mjResource_ { char* name; // name of resource (filename, etc) void* data; // opaque data pointer + char timestamp[512]; // timestamp of the resource const struct mjpResourceProvider* provider; // pointer to the provider }; typedef struct mjResource_ mjResource; @@ -2406,10 +2408,11 @@ typedef struct mjvSceneState_ mjvSceneState; //----------------------------- MJAPI FUNCTIONS -------------------------------- void mj_defaultVFS(mjVFS* vfs); int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename); -int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize); +int mj_addBufferVFS(mjVFS* vfs, const char* name, const void* buffer, int nbuffer); int mj_findFileVFS(const mjVFS* vfs, const char* filename); int mj_deleteFileVFS(mjVFS* vfs, const char* filename); void mj_deleteVFS(mjVFS* vfs); +int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize); mjModel* mj_loadXML(const char* filename, const mjVFS* vfs, char* error, int error_sz); int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int error_sz); void mj_freeLastXML(void); diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index a5bdca11..9055dca0 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -16,6 +16,7 @@ #define MUJOCO_MJMODEL_H_ #include +#include #include @@ -390,11 +391,12 @@ 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 - size_t 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 + uint64_t filestamp[mjMAXVFS]; // checksum of the file data }; typedef struct mjVFS_ mjVFS; diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h index ca850d57..b6897080 100644 --- a/include/mujoco/mjplugin.h +++ b/include/mujoco/mjplugin.h @@ -26,11 +26,12 @@ struct mjResource_ { char* name; // name of resource (filename, etc) void* data; // opaque data pointer + char timestamp[512]; // timestamp of the resource const struct mjpResourceProvider* provider; // pointer to the provider }; typedef struct mjResource_ mjResource; -// callback for opeing a resource, returns zero on failure +// callback for opening a resource, returns zero on failure typedef int (*mjfOpenResource)(mjResource* resource); // callback for reading a resource @@ -44,10 +45,12 @@ typedef void (*mjfCloseResource)(mjResource* resource); // sets dir to directory string with ndir being size of directory string typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* ndir); -// callback for checking if a resource was modified since last read -// returns > 0 if resource was modified since last open, 0 if resource was not -// modified, and < 0 if inconclusive -typedef int (*mjfResourceModified)(const mjResource* resource); +// callback for checking if the current resource was modified from the time +// specified by the timestamp +// returns 0 if the resource's timestamp matches the provided timestamp +// returns > 0 if the the resource is younger than the given timestamp +// returns < 0 if the resource is older than the given timestamp +typedef int (*mjfResourceModified)(const mjResource* resource, const char* timestamp); // struct describing a single resource provider struct mjpResourceProvider { diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index ecfaee76..f32d6b5e 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -82,8 +82,8 @@ MJAPI void mj_defaultVFS(mjVFS* vfs); // Add file to VFS, return 0: success, 1: full, 2: repeated name, -1: failed to load. MJAPI int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename); -// Make empty file in VFS, return 0: success, 1: full, 2: repeated name. -MJAPI int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize); +// Add file to VFS from buffer, return 0: success, 1: full, 2: repeated name, -1: failed to load. +MJAPI int mj_addBufferVFS(mjVFS* vfs, const char* name, const void* buffer, int nbuffer); // Return file index in VFS, or -1 if not found in VFS. MJAPI int mj_findFileVFS(const mjVFS* vfs, const char* filename); @@ -94,6 +94,8 @@ MJAPI int mj_deleteFileVFS(mjVFS* vfs, const char* filename); // Delete all files from VFS. MJAPI void mj_deleteVFS(mjVFS* vfs); +// deprecated: use mj_copyBufferVFS. +MJAPI int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize); //---------------------------------- Parse and compile --------------------------------------------- diff --git a/introspect/functions.py b/introspect/functions.py index 9ad7bd08..b2f7655f 100644 --- a/introspect/functions.py +++ b/introspect/functions.py @@ -66,9 +66,9 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Add file to VFS, return 0: success, 1: full, 2: repeated name, -1: failed to load.', # pylint: disable=line-too-long )), - ('mj_makeEmptyFileVFS', + ('mj_addBufferVFS', FunctionDecl( - name='mj_makeEmptyFileVFS', + name='mj_addBufferVFS', return_type=ValueType(name='int'), parameters=( FunctionParameterDecl( @@ -78,17 +78,23 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), ), FunctionParameterDecl( - name='filename', + name='name', type=PointerType( inner_type=ValueType(name='char', is_const=True), ), ), FunctionParameterDecl( - name='filesize', + name='buffer', + type=PointerType( + inner_type=ValueType(name='void', is_const=True), + ), + ), + FunctionParameterDecl( + name='nbuffer', type=ValueType(name='int'), ), ), - doc='Make empty file in VFS, return 0: success, 1: full, 2: repeated name.', # pylint: disable=line-too-long + doc='Add file to VFS from buffer, return 0: success, 1: full, 2: repeated name, -1: failed to load.', # pylint: disable=line-too-long )), ('mj_findFileVFS', FunctionDecl( @@ -144,6 +150,30 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Delete all files from VFS.', )), + ('mj_makeEmptyFileVFS', + FunctionDecl( + name='mj_makeEmptyFileVFS', + return_type=ValueType(name='int'), + parameters=( + FunctionParameterDecl( + name='vfs', + type=PointerType( + inner_type=ValueType(name='mjVFS'), + ), + ), + FunctionParameterDecl( + name='filename', + type=PointerType( + inner_type=ValueType(name='char', is_const=True), + ), + ), + FunctionParameterDecl( + name='filesize', + type=ValueType(name='int'), + ), + ), + doc='deprecated: use mj_copyBufferVFS.', + )), ('mj_loadXML', FunctionDecl( name='mj_loadXML', diff --git a/introspect/structs.py b/introspect/structs.py index fee5933b..67c44261 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -121,6 +121,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='buffer with file data', ), + StructFieldDecl( + name='filestamp', + type=ArrayType( + inner_type=ValueType(name='uint64_t'), + extents=(2000,), + ), + doc='checksum of the file data', + ), ), )), ('mjOption', diff --git a/src/engine/engine_resource.c b/src/engine/engine_resource.c index c1dd3174..03919c3f 100644 --- a/src/engine/engine_resource.c +++ b/src/engine/engine_resource.c @@ -105,7 +105,9 @@ mjResource* mju_openResource(const char* name, char* error, size_t error_sz) { memcpy(&fb->mtime, &file_stat.st_mtime, sizeof(time_t)); } else { memset(&fb->mtime, 0, sizeof(time_t)); + resource->timestamp[0] = '\0'; } + strftime(resource->timestamp, 512, "%Y-%m-%d-%H:%M:%S", localtime(&(fb->mtime))); return resource; } @@ -185,24 +187,20 @@ static int mju_isModifiedFile(const char* name, const file_buffer* fb) { if (stat(name, &file_stat) == 0) { return difftime(fb->mtime, file_stat.st_mtime) < 0; } - return -1; } - return -2; + return 1; // modified (default) } -// Returns > 0 if resource has been modified since last read, 0 if not, and < 0 -// if inconclusive -int mju_isModifiedResource(const mjResource* resource) { - if (resource == NULL) { - return -2; - } - +// return 0 if the resource's timestamp matches the provided timestamp +// return > 0 if the the resource is younger than the given timestamp +// return < 0 if the resource is older than the given timestamp +int mju_isModifiedResource(const mjResource* resource, const char* timestamp) { // provider is not OS filesystem if (resource->provider) { if (resource->provider->modified) { - return resource->provider->modified(resource); + return resource->provider->modified(resource, timestamp); } return 1; // default (modified) } diff --git a/src/engine/engine_resource.h b/src/engine/engine_resource.h index 96f35015..bddd2787 100644 --- a/src/engine/engine_resource.h +++ b/src/engine/engine_resource.h @@ -35,12 +35,13 @@ MJAPI void mju_closeResource(mjResource* resource); // return negative value if error MJAPI int mju_readResource(mjResource* resource, const void** buffer); -// sets for a resource with a name partitioned as {dir}{filename}, the dir and ndir pointers +// set 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); +// return 0 if the resource's timestamp matches the provided timestamp +// return > 0 if the the resource is younger than the given timestamp +// return < 0 if the resource is older than the given timestamp +MJAPI int mju_isModifiedResource(const mjResource* resource, const char* timestamp); // get the length of the dirname portion of a given path int mju_dirnamelen(const char* path); diff --git a/src/engine/engine_vfs.c b/src/engine/engine_vfs.c index 26db392e..aa8924bc 100644 --- a/src/engine/engine_vfs.c +++ b/src/engine/engine_vfs.c @@ -15,7 +15,9 @@ #include "engine/engine_vfs.h" #include +#include #include +#include #include "engine/engine_array_safety.h" #include "engine/engine_resource.h" @@ -55,6 +57,38 @@ static void vfs_strippath(char* newname, const char* oldname) { +// copies data into a buffer and produces a hash of the data +static uint64_t vfs_memcpy(void* dest, const void* restrict src, size_t n) { + uint64_t hash = 0xcbf29ce484222325; // magic number + uint64_t prime = 0x100000001b3; // magic prime + const uint8_t* bytes = (uint8_t*) src; + uint8_t* buffer = (uint8_t*) dest; + for (size_t i = 0; i < n; i++) { + buffer[i] = bytes[i]; + + // do FNV-1 hash + hash |= bytes[i]; + hash *= prime; + } + return hash; +} + + + +// VFS hash function implemented using the FNV-1 hash +static uint64_t vfs_hash(const void* restrict buffer, size_t n) { + uint64_t hash = 0xcbf29ce484222325; // magic number + uint64_t prime = 0x100000001b3; // magic prime + const uint8_t* bytes = (uint8_t*) buffer; + for (size_t i = 0; i < n; i++) { + hash |= bytes[i]; + hash *= prime; + } + return hash; +} + + + // initialize to empty (no deallocation) void mj_defaultVFS(mjVFS* vfs) { memset(vfs, 0, sizeof(mjVFS)); @@ -95,7 +129,9 @@ int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename) { if (!vfs->filedata[vfs->nfile]) { return -1; } - // assign size and count + + // assign size, count, and checksum + vfs->filestamp[vfs->nfile] = vfs_hash(vfs->filedata[vfs->nfile], filesize); vfs->filesize[vfs->nfile] = filesize; vfs->nfile++; @@ -136,6 +172,7 @@ int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize) { mjERROR("could not allocate memory"); } memset(vfs->filedata[vfs->nfile], 0, filesize); + vfs->filestamp[vfs->nfile] = 0; // assign size and count vfs->filesize[vfs->nfile] = filesize; @@ -146,6 +183,51 @@ int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize) { +// add file from buffer into VFS +int mj_addBufferVFS(mjVFS* vfs, const char* name, const void* buffer, int nbuffer) { + if (!vfs || !buffer || !name) { + mjERROR("null pointer"); + } + + if (vfs->nfile >= mjMAXVFS-1) { + return 1; + } + + // check buffer size + if (nbuffer <= 0) { + mjERROR("expects positive buffer size"); + } + + // strip path + char newname[mjMAXVFSNAME]; + vfs_strippath(newname, name); + + // check for repeated name + for (int i=0; i < vfs->nfile; i++) { + if (strncmp(newname, vfs->filename[i], mjMAXVFSNAME) == 0) { + return 2; + } + } + + // assign name + mjSTRNCPY(vfs->filename[vfs->nfile], newname); + + // allocate and clear + vfs->filedata[vfs->nfile] = mju_malloc(nbuffer); + if (!vfs->filedata[vfs->nfile]) { + mjERROR("could not allocate memory"); + } + vfs->filestamp[vfs->nfile] = vfs_memcpy(vfs->filedata[vfs->nfile], buffer, nbuffer); + + // assign size and count + vfs->filesize[vfs->nfile] = nbuffer; + vfs->nfile++; + + return 0; +} + + + // return file index in VFS, or -1 if not found in VFS int mj_findFileVFS(const mjVFS* vfs, const char* filename) { // strip path @@ -216,7 +298,13 @@ static int vfs_open_callback(mjResource* resource) { } const mjVFS* vfs = (const mjVFS*) resource->data; - return mj_findFileVFS(vfs, resource->name) >= 0; + int i = mj_findFileVFS(vfs, resource->name); + resource->timestamp[0] = '\0'; + if (i >= 0 && vfs->filestamp[i]) { + mju_encodeBase64(resource->timestamp, (uint8_t*) &vfs->filestamp[i], + sizeof(uint64_t)); + } + return i >= 0; } diff --git a/src/engine/engine_vfs.h b/src/engine/engine_vfs.h index b9b578a1..1276c9a5 100644 --- a/src/engine/engine_vfs.h +++ b/src/engine/engine_vfs.h @@ -15,6 +15,8 @@ #ifndef MUJOCO_SRC_ENGINE_ENGINE_VFS_H_ #define MUJOCO_SRC_ENGINE_ENGINE_VFS_H_ +#include + #include #include #include @@ -29,9 +31,12 @@ MJAPI void mj_defaultVFS(mjVFS* vfs); // add file to VFS, return 0: success, 1: full, 2: repeated name, -1: not found on disk MJAPI int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename); -// make empty file in VFS, return 0: success, 1: full, 2: repeated name +// deprecated: use mj_copyBufferVFS MJAPI int mj_makeEmptyFileVFS(mjVFS* vfs, const char* filename, int filesize); +// add file from buffer into VFS, return 0: success, 1: full, 2: repeated name, -1: failed to load +MJAPI int mj_copyBufferVFS(mjVFS* vfs, const char* filename, const void* buffer, int nbuffer); + // return file index in VFS, or -1 if not found in VFS MJAPI int mj_findFileVFS(const mjVFS* vfs, const char* filename); diff --git a/test/engine/engine_util_misc_test.cc b/test/engine/engine_util_misc_test.cc index 97557914..63632349 100644 --- a/test/engine/engine_util_misc_test.cc +++ b/test/engine/engine_util_misc_test.cc @@ -247,7 +247,6 @@ TEST_F(Base64Test, mju_encodeBase64) { EXPECT_THAT(buffer.data(), StrEq("D4a+//A=")); EXPECT_THAT(n, std::strlen(buffer.data()) + 1); EXPECT_THAT(n, buffer.size()); - } TEST_F(Base64Test, mju_encodeBase64_align0) { diff --git a/test/engine/engine_vfs_test.cc b/test/engine/engine_vfs_test.cc index 1e3a7996..93249055 100644 --- a/test/engine/engine_vfs_test.cc +++ b/test/engine/engine_vfs_test.cc @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include +#include #include #include @@ -25,7 +28,7 @@ namespace { using ::testing::NotNull; using EngineVfsTest = MujocoTest; -TEST_F(EngineVfsTest, AddFileVFS) { +TEST_F(EngineVfsTest, AddFileTest) { constexpr char path[] = "engine/testdata/actuation/"; const std::string dir = GetTestDataFilePath(path); std::string file1 = "activation.xml"; @@ -44,38 +47,51 @@ TEST_F(EngineVfsTest, AddFileVFS) { ASSERT_THAT(fp3, NotNull()) << "Input file3 missing."; std::fclose(fp3); - auto mj_vfs = std::make_unique(); - mj_defaultVFS(mj_vfs.get()); + auto vfs = std::make_unique(); + mj_defaultVFS(vfs.get()); - EXPECT_THAT(mj_vfs->nfile, 0); - EXPECT_THAT(mj_addFileVFS(mj_vfs.get(), dir.c_str(), file1.c_str()), 0); - EXPECT_THAT(mj_vfs->nfile, 1); - EXPECT_THAT(mj_vfs->filename[0], file1); + EXPECT_THAT(vfs->nfile, 0); + EXPECT_THAT(mj_addFileVFS(vfs.get(), dir.c_str(), file1.c_str()), 0); + EXPECT_THAT(vfs->nfile, 1); + EXPECT_THAT(vfs->filename[0], file1); - EXPECT_THAT(mj_addFileVFS(mj_vfs.get(), dir.c_str(), file2.c_str()), 0); - EXPECT_THAT(mj_vfs->nfile, 2); - EXPECT_THAT(mj_vfs->filename[0], file1); - EXPECT_THAT(mj_vfs->filename[1], file2); + EXPECT_THAT(mj_addFileVFS(vfs.get(), dir.c_str(), file2.c_str()), 0); + EXPECT_THAT(vfs->nfile, 2); + EXPECT_THAT(vfs->filename[0], file1); + EXPECT_THAT(vfs->filename[1], file2); - EXPECT_THAT(mj_addFileVFS(mj_vfs.get(), dir.c_str(), file3.c_str()), 0); - EXPECT_THAT(mj_vfs->nfile, 3); - EXPECT_THAT(mj_vfs->filename[0], file1); - EXPECT_THAT(mj_vfs->filename[1], file2); - EXPECT_THAT(mj_vfs->filename[2], file3); + EXPECT_THAT(mj_addFileVFS(vfs.get(), dir.c_str(), file3.c_str()), 0); + EXPECT_THAT(vfs->nfile, 3); + EXPECT_THAT(vfs->filename[0], file1); + EXPECT_THAT(vfs->filename[1], file2); + EXPECT_THAT(vfs->filename[2], file3); - mj_deleteFileVFS(mj_vfs.get(), file1.c_str()); - EXPECT_THAT(mj_vfs->nfile, 2); - EXPECT_THAT(mj_vfs->filename[0], file2); - EXPECT_THAT(mj_vfs->filename[1], file3); + mj_deleteFileVFS(vfs.get(), file1.c_str()); + EXPECT_THAT(vfs->nfile, 2); + EXPECT_THAT(vfs->filename[0], file2); + EXPECT_THAT(vfs->filename[1], file3); - mj_deleteFileVFS(mj_vfs.get(), file3.c_str()); - EXPECT_THAT(mj_vfs->nfile, 1); - EXPECT_THAT(mj_vfs->filename[0], file2); + mj_deleteFileVFS(vfs.get(), file3.c_str()); + EXPECT_THAT(vfs->nfile, 1); + EXPECT_THAT(vfs->filename[0], file2); - mj_deleteFileVFS(mj_vfs.get(), file2.c_str()); - EXPECT_THAT(mj_vfs->nfile, 0); + mj_deleteFileVFS(vfs.get(), file2.c_str()); + EXPECT_THAT(vfs->nfile, 0); - mj_deleteVFS(mj_vfs.get()); + mj_deleteVFS(vfs.get()); +} + +TEST_F(EngineVfsTest, AddBufferTest) { + auto vfs = std::make_unique(); + mj_defaultVFS(vfs.get()); + std::string buffer = ""; + mj_addBufferVFS(vfs.get(), "model", static_cast(buffer.c_str()), + buffer.size()); + std::array error; + mjModel* model = mj_loadXML("model", vfs.get(), error.data(), error.size()); + EXPECT_THAT(model, NotNull()); + mj_deleteModel(model); + mj_deleteVFS(vfs.get()); } } // namespace diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index a5a1219e..e8d6cabf 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -4938,6 +4938,7 @@ public unsafe struct _mjVFS [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000 * 1000)] public char[] filename; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public UIntPtr[] filesize; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public IntPtr[] filedata; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public UInt64[] filestamp; } [StructLayout(LayoutKind.Sequential)] @@ -6321,7 +6322,7 @@ public static unsafe extern void mj_defaultVFS(void* vfs); public static unsafe extern int mj_addFileVFS(void* vfs, [MarshalAs(UnmanagedType.LPStr)]string directory, [MarshalAs(UnmanagedType.LPStr)]string filename); [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] -public static unsafe extern int mj_makeEmptyFileVFS(void* vfs, [MarshalAs(UnmanagedType.LPStr)]string filename, int filesize); +public static unsafe extern int mj_addBufferVFS(void* vfs, [MarshalAs(UnmanagedType.LPStr)]string name, void* buffer, int nbuffer); [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern int mj_findFileVFS(void* vfs, [MarshalAs(UnmanagedType.LPStr)]string filename); @@ -6332,6 +6333,9 @@ public static unsafe extern int mj_deleteFileVFS(void* vfs, [MarshalAs(Unmanaged [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern void mj_deleteVFS(void* vfs); +[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] +public static unsafe extern int mj_makeEmptyFileVFS(void* vfs, [MarshalAs(UnmanagedType.LPStr)]string filename, int filesize); + [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern mjModel_* mj_loadXML([MarshalAs(UnmanagedType.LPStr)]string filename, void* vfs, StringBuilder error, int error_sz);