Add VFS handling logic to mj_openResource.
PiperOrigin-RevId: 653124909 Change-Id: I4d031eb0bfb0070f8493753d2aeaac60ac283b85
This commit is contained in:
committed by
Copybara-Service
parent
7f46e936f6
commit
2cf352c2e6
@@ -33,7 +33,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "lodepng.h"
|
#include "lodepng.h"
|
||||||
#include <mujoco/mjmacro.h>
|
|
||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include <mujoco/mjplugin.h>
|
#include <mujoco/mjplugin.h>
|
||||||
#include <mujoco/mjtnum.h>
|
#include <mujoco/mjtnum.h>
|
||||||
@@ -45,7 +44,6 @@
|
|||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "user/user_resource.h"
|
#include "user/user_resource.h"
|
||||||
#include "user/user_util.h"
|
#include "user/user_util.h"
|
||||||
#include "user/user_vfs.h"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
namespace mju = ::mujoco::util;
|
namespace mju = ::mujoco::util;
|
||||||
@@ -704,18 +702,14 @@ void mjCBase::NameSpace(const mjCModel* m) {
|
|||||||
|
|
||||||
// load resource if found (fallback to OS filesystem)
|
// load resource if found (fallback to OS filesystem)
|
||||||
mjResource* mjCBase::LoadResource(std::string filename, const mjVFS* vfs) {
|
mjResource* mjCBase::LoadResource(std::string filename, const mjVFS* vfs) {
|
||||||
// try reading from provided VFS
|
// try reading from provided VFS or fallback to OS filesystem
|
||||||
mjResource* r = mju_openVfsResource(filename.c_str(), vfs);
|
std::array<char, 1024> error;
|
||||||
|
mjResource* resource = mju_openResource(filename.c_str(), vfs,
|
||||||
if (!r) {
|
error.data(), error.size());
|
||||||
std::array<char, 1024> error;
|
if (!resource) {
|
||||||
// not in vfs try a provider or fallback to OS filesystem
|
throw mjCError(nullptr, "%s", error.data());
|
||||||
r = mju_openResource(filename.c_str(), error.data(), error.size());
|
|
||||||
if (!r) {
|
|
||||||
throw mjCError(nullptr, "%s", error.data());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return r;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+105
-132
@@ -18,7 +18,6 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <climits>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -35,12 +34,16 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mujoco/mjplugin.h>
|
#include <mujoco/mjplugin.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
#include "engine/engine_plugin.h"
|
#include "engine/engine_plugin.h"
|
||||||
#include "engine/engine_util_errmem.h"
|
|
||||||
#include "engine/engine_util_misc.h"
|
#include "engine/engine_util_misc.h"
|
||||||
|
#include "user/user_util.h"
|
||||||
|
#include "user/user_vfs.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using mujoco::user::FileToMemory;
|
||||||
|
|
||||||
// file buffer used internally for the OS filesystem
|
// file buffer used internally for the OS filesystem
|
||||||
struct FileSpec {
|
struct FileSpec {
|
||||||
bool is_read; // set to nonzero if buffer was read into
|
bool is_read; // set to nonzero if buffer was read into
|
||||||
@@ -48,11 +51,74 @@ struct FileSpec {
|
|||||||
time_t mtime; // last modified time
|
time_t mtime; // last modified time
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// callback for opening a resource, returns zero on failure
|
||||||
|
int FileOpen(mjResource* resource, char* error, size_t nerror) {
|
||||||
|
resource->provider = nullptr;
|
||||||
|
resource->data = new FileSpec;
|
||||||
|
const char* name = resource->name;
|
||||||
|
FileSpec* spec = (FileSpec*) resource->data;
|
||||||
|
spec->is_read = false;
|
||||||
|
struct stat file_stat;
|
||||||
|
if (stat(name, &file_stat) == 0) {
|
||||||
|
memcpy(&spec->mtime, &file_stat.st_mtime, sizeof(time_t));
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
snprintf(error, nerror, "Error opening file '%s': %s", name,
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
mju_encodeBase64(resource->timestamp, (uint8_t*) &spec->mtime,
|
||||||
|
sizeof(time_t));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS filesystem read callback
|
||||||
|
int FileRead(mjResource* resource, const void** buffer) {
|
||||||
|
FileSpec* spec = (FileSpec*) resource->data;
|
||||||
|
|
||||||
|
// only read once from file
|
||||||
|
if (!spec->is_read) {
|
||||||
|
spec->buffer = FileToMemory(resource->name);
|
||||||
|
spec->is_read = true;
|
||||||
|
}
|
||||||
|
*buffer = spec->buffer.data();
|
||||||
|
return spec->buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS filesystem close callback
|
||||||
|
void FileClose(mjResource* resource) {
|
||||||
|
FileSpec* spec = (FileSpec*) resource->data;
|
||||||
|
if (spec) delete spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS filesystem getdir callback
|
||||||
|
void FileGetDir(mjResource* resource, const char** dir, int* ndir) {
|
||||||
|
*dir = resource->name;
|
||||||
|
*ndir = mjuu_dirnamelen(resource->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS filesystem modified callback
|
||||||
|
int FileModified(const mjResource* resource, const char*timestamp) {
|
||||||
|
if (mju_isValidBase64(timestamp) != sizeof(time_t)) {
|
||||||
|
return 1; // error (assume modified)
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t time1, time2;
|
||||||
|
mju_decodeBase64((uint8_t*) &time1, timestamp);
|
||||||
|
time2 = ((FileSpec*) resource->data)->mtime;
|
||||||
|
double diff = difftime(time2, time1);
|
||||||
|
if (diff < 0) return -1;
|
||||||
|
if (diff > 0) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// open the given resource; if the name doesn't have a prefix matching with a
|
// open the given resource; if the name doesn't have a prefix matching with a
|
||||||
// resource provider, then the OS filesystem is used
|
// resource provider, then the OS filesystem is used
|
||||||
mjResource* mju_openResource(const char* name, char* error, size_t nerror) {
|
mjResource* mju_openResource(const char* name, const mjVFS* vfs,
|
||||||
|
char* error, size_t nerror) {
|
||||||
// no error so far
|
// no error so far
|
||||||
if (error) {
|
if (error) {
|
||||||
error[0] = '\0';
|
error[0] = '\0';
|
||||||
@@ -60,7 +126,10 @@ mjResource* mju_openResource(const char* name, char* error, size_t nerror) {
|
|||||||
|
|
||||||
mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource));
|
mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource));
|
||||||
if (resource == nullptr) {
|
if (resource == nullptr) {
|
||||||
mjERROR("could not allocate memory");
|
if (error) {
|
||||||
|
strncpy(error, "could not allocate memory", nerror);
|
||||||
|
error[nerror - 1] = '\0';
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,16 +139,30 @@ mjResource* mju_openResource(const char* name, char* error, size_t nerror) {
|
|||||||
// copy name
|
// copy name
|
||||||
resource->name = (char*) mju_malloc(sizeof(char) * (strlen(name) + 1));
|
resource->name = (char*) mju_malloc(sizeof(char) * (strlen(name) + 1));
|
||||||
if (resource->name == nullptr) {
|
if (resource->name == nullptr) {
|
||||||
|
if (error) {
|
||||||
|
strncpy(error, "could not allocate memory", nerror);
|
||||||
|
error[nerror - 1] = '\0';
|
||||||
|
}
|
||||||
mju_closeResource(resource);
|
mju_closeResource(resource);
|
||||||
mjERROR("could not allocate memory");
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1));
|
memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1));
|
||||||
|
|
||||||
|
// first priority is to check the VFS
|
||||||
|
if (vfs != nullptr) {
|
||||||
|
const mjpResourceProvider* provider = GetVfsResourceProvider();
|
||||||
|
resource->data = (void*) vfs;
|
||||||
|
resource->provider = provider;
|
||||||
|
if (provider->open(resource)) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// find provider based off prefix of name
|
// find provider based off prefix of name
|
||||||
const mjpResourceProvider* provider = mjp_getResourceProvider(name);
|
const mjpResourceProvider* provider = mjp_getResourceProvider(name);
|
||||||
if (provider != nullptr) {
|
if (provider != nullptr) {
|
||||||
resource->provider = provider;
|
resource->provider = provider;
|
||||||
|
resource->data = nullptr;
|
||||||
if (provider->open(resource)) {
|
if (provider->open(resource)) {
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
@@ -95,24 +178,11 @@ mjResource* mju_openResource(const char* name, char* error, size_t nerror) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lastly fallback to OS filesystem
|
// lastly fallback to OS filesystem
|
||||||
resource->provider = nullptr;
|
if (FileOpen(resource, error, nerror)) {
|
||||||
resource->data = new FileSpec;
|
return resource;
|
||||||
FileSpec* spec = (FileSpec*) resource->data;
|
|
||||||
spec->is_read = false;
|
|
||||||
struct stat file_stat;
|
|
||||||
if (stat(name, &file_stat) == 0) {
|
|
||||||
memcpy(&spec->mtime, &file_stat.st_mtime, sizeof(time_t));
|
|
||||||
} else {
|
|
||||||
if (error) {
|
|
||||||
snprintf(error, nerror, "Error opening file '%s': %s", name,
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
mju_closeResource(resource);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
mju_encodeBase64(resource->timestamp, (uint8_t*) &spec->mtime,
|
mju_closeResource(resource);
|
||||||
sizeof(time_t));
|
return nullptr;
|
||||||
return resource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -124,14 +194,11 @@ void mju_closeResource(mjResource* resource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use the resource provider close callback
|
// use the resource provider close callback
|
||||||
if (resource->provider && resource->provider->close) {
|
const mjpResourceProvider* provider = resource->provider;
|
||||||
resource->provider->close(resource);
|
if (provider) {
|
||||||
|
if (provider->close) provider->close(resource);
|
||||||
} else {
|
} else {
|
||||||
// clear OS filesystem if present
|
FileClose(resource); // clear OS filesystem if present
|
||||||
FileSpec* spec = (FileSpec*) resource->data;
|
|
||||||
if (spec) {
|
|
||||||
delete spec;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// free resource
|
// free resource
|
||||||
@@ -149,36 +216,26 @@ int mju_readResource(mjResource* resource, const void** buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if provider is NULL, then OS filesystem is used
|
// if provider is NULL, then OS filesystem is used
|
||||||
FileSpec* spec = (FileSpec*) resource->data;
|
return FileRead(resource, buffer);
|
||||||
|
|
||||||
// only read once from file
|
|
||||||
if (!spec->is_read) {
|
|
||||||
spec->buffer = mju_fileToMemory(resource->name);
|
|
||||||
spec->is_read = true;
|
|
||||||
}
|
|
||||||
*buffer = spec->buffer.data();
|
|
||||||
return spec->buffer.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get directory path of resource
|
// get directory path of resource
|
||||||
void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) {
|
void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) {
|
||||||
*dir = NULL;
|
*dir = nullptr;
|
||||||
*ndir = 0;
|
*ndir = 0;
|
||||||
|
|
||||||
if (resource == NULL) {
|
if (resource == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// provider is not OS filesystem
|
const mjpResourceProvider* provider = resource->provider;
|
||||||
if (resource->provider) {
|
if (provider) {
|
||||||
if (resource->provider->getdir) {
|
if (provider->getdir) provider->getdir(resource, dir, ndir);
|
||||||
resource->provider->getdir(resource, dir, ndir);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
*dir = resource->name;
|
// fallback to OS filesystem
|
||||||
*ndir = mju_dirnamelen(resource->name);
|
FileGetDir(resource, dir, ndir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,89 +254,5 @@ int mju_isModifiedResource(const mjResource* resource, const char* timestamp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fallback to OS filesystem
|
// fallback to OS filesystem
|
||||||
if (mju_isValidBase64(timestamp) != sizeof(time_t)) {
|
return FileModified(resource, timestamp);
|
||||||
return 1; // error (assume modified)
|
|
||||||
}
|
|
||||||
|
|
||||||
time_t time1, time2;
|
|
||||||
mju_decodeBase64((uint8_t*) &time1, timestamp);
|
|
||||||
time2 = ((FileSpec*) resource->data)->mtime;
|
|
||||||
double diff = difftime(time2, time1);
|
|
||||||
if (diff < 0) return -1;
|
|
||||||
if (diff > 0) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get the length of the dirname portion of a given path
|
|
||||||
int mju_dirnamelen(const char* path) {
|
|
||||||
if (!path) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pos = -1;
|
|
||||||
for (int i = 0; path[i]; ++i) {
|
|
||||||
if (path[i] == '/' || path[i] == '\\') {
|
|
||||||
pos = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// read file into memory buffer (allocated here with mju_malloc)
|
|
||||||
std::vector<uint8_t> mju_fileToMemory(const char* filename) {
|
|
||||||
FILE* fp = fopen(filename, "rb");
|
|
||||||
if (!fp) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// find size
|
|
||||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Failed to calculate size for '%s'", filename);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {};
|
|
||||||
} else if (long_filesize < 0) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Failed to calculate size for '%s'", filename);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> buffer(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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate and read
|
|
||||||
std::size_t bytes_read = fread(buffer.data(), 1, buffer.size(), fp);
|
|
||||||
|
|
||||||
// check that read data matches file size
|
|
||||||
if (bytes_read != buffer.size()) { // SHOULD NOT OCCUR
|
|
||||||
if (ferror(fp)) {
|
|
||||||
fclose(fp);
|
|
||||||
mju_warning("Read error while reading '%s'", filename);
|
|
||||||
return {};
|
|
||||||
} else if (feof(fp)) {
|
|
||||||
buffer.resize(bytes_read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close file, return contents
|
|
||||||
fclose(fp);
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ extern "C" {
|
|||||||
|
|
||||||
// open the given resource; if the name doesn't have a prefix matching with a
|
// open the given resource; if the name doesn't have a prefix matching with a
|
||||||
// resource provider, then the OS filesystem is used
|
// resource provider, then the OS filesystem is used
|
||||||
MJAPI mjResource* mju_openResource(const char* name, char* error, std::size_t nerror);
|
MJAPI mjResource* mju_openResource(const char* name, const mjVFS* vfs,
|
||||||
|
char* error, std::size_t nerror);
|
||||||
|
|
||||||
// close the given resource; no-op if resource is NULL
|
// close the given resource; no-op if resource is NULL
|
||||||
MJAPI void mju_closeResource(mjResource* resource);
|
MJAPI void mju_closeResource(mjResource* resource);
|
||||||
@@ -47,14 +48,8 @@ MJAPI void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir)
|
|||||||
// return < 0 if the resource is older 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);
|
MJAPI int mju_isModifiedResource(const mjResource* resource, const char* timestamp);
|
||||||
|
|
||||||
// get the length of the dirname portion of a given path
|
|
||||||
MJAPI int mju_dirnamelen(const char* path);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// read file into memory buffer (allocated here with mju_malloc)
|
|
||||||
std::vector<uint8_t> mju_fileToMemory(const char* filename);
|
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
|
#endif // MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
|
||||||
|
|||||||
+72
-1
@@ -16,8 +16,10 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <climits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -941,6 +943,22 @@ std::string mjuu_extToContentType(std::string_view filename) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the length of the dirname portion of a given path
|
||||||
|
int mjuu_dirnamelen(const char* path) {
|
||||||
|
if (!path) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = -1;
|
||||||
|
for (int i = 0; path[i]; ++i) {
|
||||||
|
if (path[i] == '/' || path[i] == '\\') {
|
||||||
|
pos = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
namespace mujoco::user {
|
namespace mujoco::user {
|
||||||
|
|
||||||
std::string FilePath::Combine(const std::string& s1, const std::string& s2) {
|
std::string FilePath::Combine(const std::string& s1, const std::string& s2) {
|
||||||
@@ -1012,7 +1030,6 @@ FilePath FilePath::StripExt() const {
|
|||||||
return FilePathFast(path_.substr(0, n));
|
return FilePathFast(path_.substr(0, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// is directory absolute path
|
// is directory absolute path
|
||||||
std::string FilePath::AbsPrefix(const std::string& str) {
|
std::string FilePath::AbsPrefix(const std::string& str) {
|
||||||
// empty: not absolute
|
// empty: not absolute
|
||||||
@@ -1066,5 +1083,59 @@ std::string FilePath::StrLower() const {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// read file into memory buffer
|
||||||
|
std::vector<uint8_t> FileToMemory(const char* filename) {
|
||||||
|
FILE* fp = fopen(filename, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// find size
|
||||||
|
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("Failed to calculate size for '%s'", filename);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {};
|
||||||
|
} else if (long_filesize < 0) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("Failed to calculate size for '%s'", filename);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> buffer(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 {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate and read
|
||||||
|
std::size_t bytes_read = fread(buffer.data(), 1, buffer.size(), fp);
|
||||||
|
|
||||||
|
// check that read data matches file size
|
||||||
|
if (bytes_read != buffer.size()) { // SHOULD NOT OCCUR
|
||||||
|
if (ferror(fp)) {
|
||||||
|
fclose(fp);
|
||||||
|
mju_warning("Read error while reading '%s'", filename);
|
||||||
|
return {};
|
||||||
|
} else if (feof(fp)) {
|
||||||
|
buffer.resize(bytes_read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close file, return contents
|
||||||
|
fclose(fp);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mujoco::user
|
} // namespace mujoco::user
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
#define MUJOCO_SRC_USER_USER_UTIL_H_
|
#define MUJOCO_SRC_USER_USER_UTIL_H_
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
const double mjEPS = 1E-14; // minimum value in various calculations
|
const double mjEPS = 1E-14; // minimum value in various calculations
|
||||||
const double mjMINMASS = 1E-6; // minimum mass allowed
|
const double mjMINMASS = 1E-6; // minimum mass allowed
|
||||||
@@ -230,6 +232,9 @@ class FilePath {
|
|||||||
std::string path_;
|
std::string path_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// read file into memory buffer
|
||||||
|
std::vector<uint8_t> FileToMemory(const char* filename);
|
||||||
|
|
||||||
} // namespace mujoco::user
|
} // namespace mujoco::user
|
||||||
|
|
||||||
// strip path from filename
|
// strip path from filename
|
||||||
@@ -258,4 +263,7 @@ std::optional<std::string_view> mjuu_parseContentTypeAttrSubtype(std::string_vie
|
|||||||
// convert filename extension to content type; return empty string if not found
|
// convert filename extension to content type; return empty string if not found
|
||||||
std::string mjuu_extToContentType(std::string_view filename);
|
std::string mjuu_extToContentType(std::string_view filename);
|
||||||
|
|
||||||
|
// get the length of the dirname portion of a given path
|
||||||
|
int mjuu_dirnamelen(const char* path);
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_USER_USER_UTIL_H_
|
#endif // MUJOCO_SRC_USER_USER_UTIL_H_
|
||||||
|
|||||||
+9
-44
@@ -17,19 +17,20 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "engine/engine_util_errmem.h"
|
|
||||||
#include "engine/engine_util_misc.h"
|
#include "engine/engine_util_misc.h"
|
||||||
#include "user/user_resource.h"
|
|
||||||
#include "user/user_util.h"
|
#include "user/user_util.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using mujoco::user::FilePath;
|
using mujoco::user::FilePath;
|
||||||
|
using mujoco::user::FileToMemory;
|
||||||
|
|
||||||
// internal struct for VFS files
|
// internal struct for VFS files
|
||||||
struct VFSFile {
|
struct VFSFile {
|
||||||
@@ -177,7 +178,7 @@ void Close(mjResource* resource) {
|
|||||||
// getdir callback for the VFS resource provider
|
// getdir callback for the VFS resource provider
|
||||||
void GetDir(mjResource* resource, const char** dir, int* ndir) {
|
void GetDir(mjResource* resource, const char** dir, int* ndir) {
|
||||||
*dir = (resource) ? resource->name : nullptr;
|
*dir = (resource) ? resource->name : nullptr;
|
||||||
*ndir = (resource) ? mju_dirnamelen(resource->name) : 0;
|
*ndir = (resource) ? mjuu_dirnamelen(resource->name) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// modified callback for the VFS resource provider
|
// modified callback for the VFS resource provider
|
||||||
@@ -227,7 +228,7 @@ int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allocate and read
|
// allocate and read
|
||||||
std::vector<uint8_t> buffer = mju_fileToMemory(fullname.c_str());
|
std::vector<uint8_t> buffer = FileToMemory(fullname.c_str());
|
||||||
if (buffer.empty()) {
|
if (buffer.empty()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -265,44 +266,8 @@ void mj_deleteVFS(mjVFS* vfs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// open VFS resource
|
const mjpResourceProvider* GetVfsResourceProvider() {
|
||||||
mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs) {
|
static mjpResourceProvider provider
|
||||||
if (vfs == nullptr) {
|
= { nullptr, &Open, &Read, &Close, &GetDir, &Modified, nullptr };
|
||||||
return nullptr;
|
return &provider;
|
||||||
}
|
|
||||||
|
|
||||||
// VFS provider
|
|
||||||
static struct mjpResourceProvider provider = { nullptr, &Open, &Read, &Close,
|
|
||||||
&GetDir, &Modified, nullptr };
|
|
||||||
|
|
||||||
// create resource
|
|
||||||
mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource));
|
|
||||||
if (resource == nullptr) {
|
|
||||||
mjERROR("could not allocate memory");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear out resource
|
|
||||||
memset(resource, 0, sizeof(mjResource));
|
|
||||||
|
|
||||||
// copy name
|
|
||||||
std::size_t n = std::strlen(name);
|
|
||||||
resource->name = (char*) mju_malloc(sizeof(char) * (n + 1));
|
|
||||||
if (resource->name == nullptr) {
|
|
||||||
mju_closeResource(resource);
|
|
||||||
mjERROR("could not allocate memory");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
std::memcpy(resource->name, name, sizeof(char) * (n + 1));
|
|
||||||
resource->data = (void*) vfs;
|
|
||||||
|
|
||||||
// open resource
|
|
||||||
resource->provider = &provider;
|
|
||||||
if (provider.open(resource)) {
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not found in VFS
|
|
||||||
mju_closeResource(resource);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -45,11 +45,10 @@ 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);
|
||||||
|
|
||||||
// open VFS resource
|
|
||||||
MJAPI mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const mjpResourceProvider* GetVfsResourceProvider();
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_USER_USER_VFS_H_
|
#endif // MUJOCO_SRC_USER_USER_VFS_H_
|
||||||
|
|||||||
+19
-27
@@ -173,27 +173,23 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
|
|||||||
} else {
|
} else {
|
||||||
fullname = filename;
|
fullname = filename;
|
||||||
}
|
}
|
||||||
mjResource *resource = mju_openVfsResource(fullname.c_str(), vfs);
|
|
||||||
if (!resource) {
|
|
||||||
// load from provider or OS filesystem
|
|
||||||
std::array<char, 1024> error;
|
|
||||||
resource = mju_openResource(fullname.c_str(), error.data(), error.size());
|
|
||||||
if (!resource) {
|
|
||||||
if (!mjuu_isabspath(filename)) {
|
|
||||||
fullname = std::string(dir) + filename;
|
|
||||||
} else {
|
|
||||||
fullname = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load from provider or OS filesystem
|
// legacy behavior: try to load in top level directory
|
||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
resource = mju_openResource(fullname.c_str(), error.data(), error.size());
|
mjResource *resource = mju_openResource(fullname.c_str(), vfs,
|
||||||
if (!resource) {
|
error.data(), error.size());
|
||||||
throw mjXError(elem, "%s", error.data());
|
if (resource == nullptr) {
|
||||||
}
|
// new behavior: try to load in relative directory
|
||||||
|
if (!mjuu_isabspath(filename)) {
|
||||||
|
fullname = std::string(dir) + filename;
|
||||||
|
resource = mju_openResource(fullname.c_str(), vfs, error.data(), error.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resource == nullptr) {
|
||||||
|
throw mjXError(elem, "%s", error.data());
|
||||||
|
}
|
||||||
|
|
||||||
if (!mjuu_isabspath(filename)) {
|
if (!mjuu_isabspath(filename)) {
|
||||||
filename = std::string(dir) + filename;
|
filename = std::string(dir) + filename;
|
||||||
}
|
}
|
||||||
@@ -293,16 +289,12 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
|
|||||||
|
|
||||||
// get data source
|
// get data source
|
||||||
const char* xmlstring = nullptr;
|
const char* xmlstring = nullptr;
|
||||||
mjResource* resource = mju_openVfsResource(filename, vfs);
|
std::array<char, 1024> rerror;
|
||||||
|
mjResource* resource = mju_openResource(filename, vfs,
|
||||||
if (!resource) {
|
rerror.data(), rerror.size());
|
||||||
// load from provider or fallback to OS filesystem
|
if (resource == nullptr) {
|
||||||
std::array<char, 1024> rerror;
|
std::snprintf(error, error_sz, "mjParseXML: %s", rerror.data());
|
||||||
resource = mju_openResource(filename, rerror.data(), rerror.size());
|
return nullptr;
|
||||||
if (!resource) {
|
|
||||||
std::snprintf(error, error_sz, "mjParseXML: %s", rerror.data());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
|
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
|
||||||
|
|||||||
+7
-9
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "xml/xml_api.h"
|
#include "xml/xml_api.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -186,15 +187,12 @@ int mj_printSchema(const char* filename, char* buffer, int buffer_sz, int flg_ht
|
|||||||
|
|
||||||
// load model from binary MJB resource
|
// load model from binary MJB resource
|
||||||
mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
|
mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
|
||||||
mjResource* resource = nullptr;
|
std::array<char, 1024> error;
|
||||||
|
mjResource* resource = mju_openResource(filename, vfs,
|
||||||
// first try vfs, otherwise try a provider or OS filesystem
|
error.data(), error.size());
|
||||||
if (!(resource = mju_openVfsResource(filename, vfs))) {
|
if (resource == nullptr) {
|
||||||
char error[1024];
|
mju_warning("%s", error.data());
|
||||||
if (!(resource = mju_openResource(filename, error, 1024))) {
|
return nullptr;
|
||||||
mju_warning("%s", error);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const void* buffer = NULL;
|
const void* buffer = NULL;
|
||||||
|
|||||||
+2
-1
@@ -169,7 +169,8 @@ static std::string ResolveFilePath(XMLElement* e, std::string filename,
|
|||||||
// TODO(kylebayes): We first look in the base model directory for files to
|
// TODO(kylebayes): We first look in the base model directory for files to
|
||||||
// remain backwards compatible.
|
// remain backwards compatible.
|
||||||
std::string full_filename = mjuu_combinePaths(dir, filename);
|
std::string full_filename = mjuu_combinePaths(dir, filename);
|
||||||
mjResource *resource = mju_openResource(full_filename.c_str(), nullptr, 0);
|
mjResource *resource = mju_openResource(full_filename.c_str(), nullptr,
|
||||||
|
nullptr, 0);
|
||||||
if (resource != nullptr) {
|
if (resource != nullptr) {
|
||||||
mju_closeResource(resource);
|
mju_closeResource(resource);
|
||||||
return filename;
|
return filename;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ void CacheText(mjCCache& cache, const std::string& model,
|
|||||||
mjVFS vfs;
|
mjVFS vfs;
|
||||||
mj_defaultVFS(&vfs);
|
mj_defaultVFS(&vfs);
|
||||||
mj_addBufferVFS(&vfs, name.c_str(), text.data(), text.size());
|
mj_addBufferVFS(&vfs, name.c_str(), text.data(), text.size());
|
||||||
mjResource* resource = mju_openVfsResource(name.c_str(), &vfs);
|
mjResource* resource = mju_openResource(name.c_str(), &vfs, nullptr, 0);
|
||||||
std::shared_ptr<const void> data(&text, +[](const void* data) {});
|
std::shared_ptr<const void> data(&text, +[](const void* data) {});
|
||||||
cache.Insert(model, resource, data, text.size());
|
cache.Insert(model, resource, data, text.size());
|
||||||
mju_closeResource(resource);
|
mju_closeResource(resource);
|
||||||
@@ -61,7 +61,7 @@ GetCachedText(mjCCache& cache, const std::string& model,
|
|||||||
mjVFS vfs;
|
mjVFS vfs;
|
||||||
mj_defaultVFS(&vfs);
|
mj_defaultVFS(&vfs);
|
||||||
mj_addBufferVFS(&vfs, name.c_str(), text.data(), std::strlen(text.c_str()));
|
mj_addBufferVFS(&vfs, name.c_str(), text.data(), std::strlen(text.c_str()));
|
||||||
mjResource* resource = mju_openVfsResource(name.c_str(), &vfs);
|
mjResource* resource = mju_openResource(name.c_str(), &vfs, nullptr, 0);
|
||||||
bool inserted = cache.PopulateData(resource,
|
bool inserted = cache.PopulateData(resource,
|
||||||
[&cached_text](const void* data) {
|
[&cached_text](const void* data) {
|
||||||
cached_text = *(static_cast<const std::string*>(data));
|
cached_text = *(static_cast<const std::string*>(data));
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ TEST_F(ResourceTest, GeneralTest) {
|
|||||||
EXPECT_GT(i, 0);
|
EXPECT_GT(i, 0);
|
||||||
|
|
||||||
// open resource
|
// open resource
|
||||||
mjResource* resource = mju_openResource("str:file", nullptr, 0);
|
mjResource* resource = mju_openResource("str:file", nullptr, nullptr, 0);
|
||||||
ASSERT_THAT(resource, NotNull());
|
ASSERT_THAT(resource, NotNull());
|
||||||
|
|
||||||
const char* buffer = NULL;
|
const char* buffer = NULL;
|
||||||
@@ -256,7 +256,7 @@ TEST_F(ResourceTest, GeneralFailureTest) {
|
|||||||
static std::array<char, 1024> error;
|
static std::array<char, 1024> error;
|
||||||
|
|
||||||
// open resource
|
// open resource
|
||||||
mjResource* resource = mju_openResource("str:notfound",
|
mjResource* resource = mju_openResource("str:notfound", nullptr,
|
||||||
error.data(), error.size());
|
error.data(), error.size());
|
||||||
ASSERT_THAT(resource, IsNull());
|
ASSERT_THAT(resource, IsNull());
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ TEST_F(ResourceTest, NameWithValidPrefix) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// open resource
|
// open resource
|
||||||
mjResource* resource = mju_openResource("nop:found", nullptr, 0);
|
mjResource* resource = mju_openResource("nop:found", nullptr, nullptr, 0);
|
||||||
ASSERT_THAT(resource, NotNull());
|
ASSERT_THAT(resource, NotNull());
|
||||||
mju_closeResource(resource);
|
mju_closeResource(resource);
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ TEST_F(ResourceTest, NameWithUpperCasePrefix) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// open resource
|
// open resource
|
||||||
mjResource* resource = mju_openResource("NOP:found", nullptr, 0);
|
mjResource* resource = mju_openResource("NOP:found", nullptr, nullptr, 0);
|
||||||
ASSERT_THAT(resource, NotNull());
|
ASSERT_THAT(resource, NotNull());
|
||||||
mju_closeResource(resource);
|
mju_closeResource(resource);
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ TEST_F(ResourceTest, NameWithInvalidPrefix) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// open resource
|
// open resource
|
||||||
mjResource* resource = mju_openResource("nopfound", nullptr, 0);
|
mjResource* resource = mju_openResource("nopfound", nullptr, nullptr, 0);
|
||||||
ASSERT_THAT(resource, IsNull());
|
ASSERT_THAT(resource, IsNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +338,8 @@ TEST_F(ResourceTest, OSFilesystemTimestamps) {
|
|||||||
const char* const file = "engine/testdata/collision_box/boxbox_deep.xml";
|
const char* const file = "engine/testdata/collision_box/boxbox_deep.xml";
|
||||||
const std::string xml_path = GetTestDataFilePath(file);
|
const std::string xml_path = GetTestDataFilePath(file);
|
||||||
|
|
||||||
mjResource* resource = mju_openResource(xml_path.c_str(), nullptr, 0);
|
mjResource* resource = mju_openResource(xml_path.c_str(), nullptr,
|
||||||
|
nullptr, 0);
|
||||||
mju_decodeBase64((uint8_t*) &t, resource->timestamp);
|
mju_decodeBase64((uint8_t*) &t, resource->timestamp);
|
||||||
|
|
||||||
// equal timestamps
|
// equal timestamps
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ using ::testing::NotNull;
|
|||||||
using UserVfsTest = MujocoTest;
|
using UserVfsTest = MujocoTest;
|
||||||
|
|
||||||
static bool HasFile(const mjVFS* vfs, const std::string& filename) {
|
static bool HasFile(const mjVFS* vfs, const std::string& filename) {
|
||||||
mjResource* resource = mju_openVfsResource(filename.c_str(), vfs);
|
mjResource* resource = mju_openResource(filename.c_str(), vfs, nullptr, 0);
|
||||||
bool result = resource != nullptr;
|
bool result = resource != nullptr;
|
||||||
mju_closeResource(resource);
|
mju_closeResource(resource);
|
||||||
return result;
|
return result;
|
||||||
@@ -222,7 +222,7 @@ TEST_F(UserVfsTest, Timestamps) {
|
|||||||
mj_defaultVFS(&vfs);
|
mj_defaultVFS(&vfs);
|
||||||
mj_addBufferVFS(&vfs, "cube.obj", cube, sizeof(cube));
|
mj_addBufferVFS(&vfs, "cube.obj", cube, sizeof(cube));
|
||||||
|
|
||||||
mjResource* resource = mju_openVfsResource("cube.obj", &vfs);
|
mjResource* resource = mju_openResource("cube.obj", &vfs, nullptr, 0);
|
||||||
|
|
||||||
// same timestamps
|
// same timestamps
|
||||||
EXPECT_EQ(mju_isModifiedResource(resource, resource->timestamp), 0);
|
EXPECT_EQ(mju_isModifiedResource(resource, resource->timestamp), 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user