diff --git a/doc/includes/references.h b/doc/includes/references.h index 35aaa77c..d6223101 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1259,6 +1259,7 @@ struct mjpResourceProvider { mjfReadResource read; // reading callback mjfCloseResource close; // closing callback mjfGetResourceDir getdir; // get directory callback (optional) + mjfResourceModified modified; // resource modified callback (optional) void* data; // opaque data pointer (resource invariant) }; typedef struct mjpResourceProvider mjpResourceProvider; diff --git a/doc/programming/extension.rst b/doc/programming/extension.rst index e5e22846..7ab8f7e2 100644 --- a/doc/programming/extension.rst +++ b/doc/programming/extension.rst @@ -330,8 +330,9 @@ Resource prefix Callbacks There are three callbacks that a resource provider is required to implement: :ref:`open`, - :ref:`read`, and :ref:`close`. A fourth callback :ref:`getdir` - is optional. More details on these callbacks are given below. + :ref:`read`, and :ref:`close`. The other two callback + :ref:`getdir` and :ref:`modified` are optional. More details on these callbacks + are given below. Data Pointer Lastly, there's an opaque data pointer for the provider to pass data into the callbacks. This data pointer is constant @@ -351,6 +352,8 @@ Resource providers work via callbacks: - :ref:`mjfGetResourceDir`: This callback is optional and is used to extract the directory from a resource name. For example, the resource name ``http://www.example.com/myasset.obj`` would have ``http://www.example.com/`` as its directory. +- :ref:`mjfModifiedResource`: This callback is optional and is used to check if an existing + opened resource has been modifed from its orginal source. .. _exProviderUsage: @@ -421,6 +424,6 @@ Now we can write assets as strings in our MJCF files: - + ... diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h index aa23bc44..2c071195 100644 --- a/include/mujoco/mjplugin.h +++ b/include/mujoco/mjplugin.h @@ -43,6 +43,11 @@ 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); + // struct describing a single resource provider struct mjpResourceProvider { const char* prefix; // prefix for match against a resource name @@ -50,6 +55,7 @@ struct mjpResourceProvider { mjfReadResource read; // reading callback mjfCloseResource close; // closing callback mjfGetResourceDir getdir; // get directory callback (optional) + mjfResourceModified modified; // resource modified callback (optional) void* data; // opaque data pointer (resource invariant) }; typedef struct mjpResourceProvider mjpResourceProvider; diff --git a/src/engine/engine_plugin.cc b/src/engine/engine_plugin.cc index 52e86455..50b93d61 100644 --- a/src/engine/engine_plugin.cc +++ b/src/engine/engine_plugin.cc @@ -269,6 +269,7 @@ bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResou p1->read == p2->read && p1->close == p2->close && p1->getdir == p2->getdir && + p1->modified == p2->modified && p1->data == p2->data); } diff --git a/src/engine/engine_resource.c b/src/engine/engine_resource.c index bd7280fa..4718df50 100644 --- a/src/engine/engine_resource.c +++ b/src/engine/engine_resource.c @@ -19,6 +19,17 @@ #include #include #include +#include +#include +#include + +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) + #include +#endif + +#ifdef _WIN32 + #define stat _stat +#endif #include #include "engine/engine_plugin.h" @@ -28,6 +39,7 @@ typedef struct { uint8_t* buffer; // raw bytes from file size_t nbuffer; // size of buffer in bytes + time_t mtime; // last modified time } file_buffer; // open the given resource; if the name doesn't have a prefix matching with a @@ -77,6 +89,13 @@ mjResource* mju_openResource(const char* name) { mju_closeResource(resource); return NULL; } + struct stat file_stat; + if (stat(name, &file_stat) == 0) { + memcpy(&fb->mtime, &file_stat.st_mtime, sizeof(time_t)); + } else { + memset(&fb->mtime, 0, sizeof(time_t)); + } + return resource; } @@ -149,6 +168,40 @@ void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) { +// modified callback for OS filesystem +static int mju_isModifiedFile(const char* name, const file_buffer* fb) { + if (fb != NULL) { + struct stat file_stat; + if (stat(name, &file_stat) == 0) { + return difftime(fb->mtime, file_stat.st_mtime) < 0; + } + return -1; + } + return -2; +} + + + +// 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; + } + + // provider is not OS filesystem + if (resource->provider) { + if (resource->provider->modified) { + return resource->provider->modified(resource); + } + return 1; // default (modified) + } + + return mju_isModifiedFile(resource->name, (file_buffer*) resource->data); +} + + + // get the length of the dirname portion of a given path int mju_dirnamelen(const char* path) { if (!path) { diff --git a/src/engine/engine_vfs.c b/src/engine/engine_vfs.c index 0535111e..26db392e 100644 --- a/src/engine/engine_vfs.c +++ b/src/engine/engine_vfs.c @@ -274,6 +274,7 @@ mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs) { .read = &vfs_read_callback, .close = &vfs_close_callback, .getdir = &vfs_getdir_callback, + .modified = NULL }; // create resource