diff --git a/src/user/user_cache.cc b/src/user/user_cache.cc index 4711a55c..35d99382 100644 --- a/src/user/user_cache.cc +++ b/src/user/user_cache.cc @@ -14,137 +14,16 @@ #include "user/user_cache.h" -#include -#include #include #include #include #include -#include #include #include #include -#include - -// copies a block of data into the asset and returns number of bytes stored -// loading data into an asset should happen in a single thread -template std::size_t mjCAsset::Add(const std::string& name, - const T* data, std::size_t n) { - auto [it, inserted] = blocks_.insert({name, mjCAssetData()}); - if (!inserted) { - return 0; - } - - std::size_t nbytes = n * sizeof(T); - const uint8_t* ptr = reinterpret_cast(data); - mjCAssetData& block = it->second; - - block.bytes = std::shared_ptr(new uint8_t[nbytes], - [](uint8_t *p) {delete [] p;}); - std::copy(ptr, ptr + nbytes, block.bytes.get()); - block.nbytes = nbytes; - nbytes_ += nbytes; - return nbytes; -} -template std::size_t mjCAsset::Add(const std::string& name, - const uint8_t* data, std::size_t n); -template std::size_t mjCAsset::Add(const std::string& name, - const int* data, std::size_t n); -template std::size_t mjCAsset::Add(const std::string& name, - const unsigned int* data, std::size_t n); -template std::size_t mjCAsset::Add(const std::string& name, - const float* data, std::size_t n); -template std::size_t mjCAsset::Add(const std::string& name, - const double* data, std::size_t n); - - - -// copies a vector into the asset and returns number of bytes stored -// loading data into an asset should happen in a single thread -template std::size_t mjCAsset::AddVector(const std::string& name, - const std::vector& v) { - return Add(name, v.data(), v.size()); -} -template std::size_t mjCAsset::AddVector(const std::string& name, - const std::vector& v); -template std::size_t mjCAsset::AddVector(const std::string& name, - const std::vector& v); -template std::size_t mjCAsset::AddVector(const std::string& name, - const std::vector& v); -template std::size_t mjCAsset::AddVector(const std::string& name, - const std::vector& v); -template std::size_t mjCAsset::AddVector(const std::string& name, - const std::vector& v); - - - -// returns a pointer to a block of data, sets n to size of data -template -const T* mjCAsset::Get(const std::string& name, std::size_t* n) const { - auto it = blocks_.find(name); - if (it == blocks_.end()) { - *n = 0; - return nullptr; - } - - const mjCAssetData& data = it->second; - - // TODO(kylebayes): This is probably caused by a user bug and needs an - // assertion for debugging purposes - if (data.nbytes % sizeof(T)) { - *n = 0; - return nullptr; - } - - *n = data.nbytes / sizeof(T); - return reinterpret_cast(data.bytes.get()); -} - -template -const uint8_t* mjCAsset::Get(const std::string& name, std::size_t* n) const; -template -const int* mjCAsset::Get(const std::string& name, std::size_t* n) const; -template -const unsigned int* mjCAsset::Get(const std::string& name, std::size_t* n) const; -template -const float* mjCAsset::Get(const std::string& name, std::size_t* n) const; -template -const double* mjCAsset::Get(const std::string& name, std::size_t* n) const; - - - -// copies a block of data into a vector -template std::optional> -mjCAsset::GetVector(const std::string& name) const { - std::size_t n; - const T* ptr = Get(name, &n); - if (ptr == nullptr) { - return std::nullopt; - } - return std::vector(ptr, ptr + n); -} - -template std::optional> -mjCAsset::GetVector(const std::string& name) const; -template std::optional> -mjCAsset::GetVector(const std::string& name) const; -template std::optional> -mjCAsset::GetVector(const std::string& name) const; -template std::optional> -mjCAsset::GetVector(const std::string& name) const; -template std::optional> -mjCAsset::GetVector(const std::string& name) const; - - - -// replaces blocks data in asset -void mjCAsset::ReplaceBlocks( - const std::unordered_map& blocks, - std::size_t nbytes) { - blocks_ = blocks; - nbytes_ = nbytes; -} +#include +#include "user/user_resource.h" // makes a copy for user (strip unnecessary items) @@ -152,8 +31,8 @@ mjCAsset mjCAsset::Copy(const mjCAsset& other) { mjCAsset asset; asset.id_ = other.Id(); asset.timestamp_ = other.Timestamp(); - asset.blocks_ = other.blocks_; - asset.nbytes_ = other.nbytes_; + asset.data_ = other.data_; + asset.size_ = other.size_; return asset; } @@ -185,97 +64,54 @@ const std::string* mjCCache::HasAsset(const std::string& id) { // inserts an asset into the cache, if asset is already in the cache, its data // is updated only if the timestamps disagree -bool mjCCache::Insert(const mjCAsset& asset) { +bool mjCCache::Insert(const std::string& modelname, const mjResource *resource, + std::shared_ptr data, std::size_t size) { std::lock_guard lock(mutex_); // check if asset is too large to fit in the cache - std::size_t nbytes = asset.BytesCount(); - const std::string& id = asset.Id(); - if ((size_ + nbytes > max_size_) && lookup_.find(id) == lookup_.end()) { + if ((size_ + size > max_size_) && + lookup_.find(resource->name) == lookup_.end()) { return false; } - - if (asset.References().size() != 1) { - return false; - } - const std::string& filename = *(asset.References().begin()); - auto [it, inserted] = lookup_.insert({id, asset}); + mjCAsset asset(modelname, resource, data, size); + auto [it, inserted] = lookup_.insert({resource->name, asset}); mjCAsset* asset_ptr = &(it->second); if (!inserted) { - if (size_ - asset_ptr->BytesCount() + nbytes > max_size_) { + if (size_ - asset_ptr->BytesCount() + size > max_size_) { return false; } - models_[filename].insert(asset_ptr); // add it for the model - asset_ptr->AddReference(filename); + models_[modelname].insert(asset_ptr); // add it for the model + asset_ptr->AddReference(modelname); if (it->second.Timestamp() == asset.Timestamp()) { return true; } asset_ptr->SetTimestamp(asset.Timestamp()); - size_ = size_ - asset_ptr->BytesCount() + nbytes; - asset_ptr->ReplaceBlocks(asset.Blocks(), nbytes); + size_ = size_ - asset_ptr->BytesCount() + size; + asset_ptr->ReplaceData(asset); return true; } // new asset asset_ptr->SetInsertNum(insert_num_++); entries_.insert(asset_ptr); - models_[filename].insert(asset_ptr); - size_ += nbytes; + models_[modelname].insert(asset_ptr); + size_ += size; return true; } -bool mjCCache::Insert(mjCAsset&& asset) { +// populate data from the cache into the given function +bool mjCCache::PopulateData(const mjResource* resource, mjCDataFunc fn) { std::lock_guard lock(mutex_); - - // check if asset is too large to fit in the cache - std::size_t nbytes = asset.BytesCount(); - const std::string& id = asset.Id(); - if ((size_ + nbytes > max_size_) && lookup_.find(id) == lookup_.end()) { - return false; - } - - if (asset.References().size() != 1) { - return false; - } - const std::string& filename = *(asset.References().begin()); - auto [it, inserted] = lookup_.try_emplace(id, std::move(asset)); - mjCAsset* asset_ptr = &(it->second); - - if (!inserted) { - if (size_ - asset_ptr->BytesCount() + nbytes > max_size_) { - return false; - } - models_[filename].insert(asset_ptr); // add it for the model - asset_ptr->AddReference(std::move(filename)); - if (it->second.Timestamp() == asset.Timestamp()) { - return true; - } - // move data and timestamp over - asset_ptr->SetTimestamp(std::move(asset.timestamp_)); - size_ = size_ - asset_ptr->BytesCount() + nbytes; - asset_ptr->ReplaceBlocks(std::move(asset.blocks_), asset.nbytes_); - return true; - } - - // new asset - asset_ptr->SetInsertNum(insert_num_++); - entries_.insert(asset_ptr); - models_[filename].insert(asset_ptr); - size_ += nbytes; - return true; -} - - - -// returns the asset with the given id, if it exists in the cache -std::optional mjCCache::Get(const std::string& id) { - std::lock_guard lock(mutex_); - auto it = lookup_.find(id); + auto it = lookup_.find(resource->name); if (it == lookup_.end()) { - return std::nullopt; + return false; + } + + if (mju_isModifiedResource(resource, it->second.Timestamp().c_str())) { + return false; } mjCAsset* asset = &(it->second); @@ -284,7 +120,9 @@ std::optional mjCCache::Get(const std::string& id) { // update priority queue entries_.erase(asset); entries_.insert(asset); - return asset->Copy(*asset); + + asset->PopulateData(fn); + return true; } diff --git a/src/user/user_cache.h b/src/user/user_cache.h index 62af3580..a2d5577f 100644 --- a/src/user/user_cache.h +++ b/src/user/user_cache.h @@ -16,38 +16,34 @@ #define MUJOCO_SRC_USER_CACHE_H_ #include -#include #include +#include #include #include #include #include -#include #include #include #include -#include -// data associated with an asset -struct mjCAssetData { - std::shared_ptr bytes; // raw serialized bytes of cached data - std::size_t nbytes; // number of bytes stored -}; +#include + +typedef std::function mjCDataFunc; +typedef void (*mjCDeallocFunc)(const void*); // A class container for a thread-safe asset cache // // Each mjCAsset is used to store raw and/or processed data loaded from a // resource and is defined by a unique ID (usually the full filename of the -// asset). The asset's data can be segregated into blocks for ease of use of -// mix and matching different types of data. Each block is given a unique name -// within the asset for readability. For example, mjCAsset for a mesh may -// include all vertex positions, edges, and the computed volume. +// asset). class mjCAsset { friend class mjCCache; public: - mjCAsset(std::string filename, std::string id, std::string timestamp) - : id_(std::move(id)), timestamp_(std::move(timestamp)) { - AddReference(filename); + mjCAsset(std::string modelname, const mjResource* resource, + std::shared_ptr data, std::size_t size) : + id_(resource->name), timestamp_(resource->timestamp), + size_(size), data_(std::move(data)) { + AddReference(modelname); } // move and copy constructors @@ -56,35 +52,16 @@ class mjCAsset { mjCAsset(const mjCAsset& other) = default; mjCAsset& operator=(const mjCAsset& other) = default; - // copies a block of data into the asset and returns number of bytes stored - // loading data into an asset should happen in a single thread - template std::size_t Add(const std::string& name, - const T* data, std::size_t n); - - - // copies a vector into the asset and returns number of bytes stored - // loading data into an asset should happen in a single thread - template std::size_t AddVector(const std::string& name, - const std::vector& v); - - // returns a pointer to a block of data, sets n to size of data - template - const T* Get(const std::string& name, std::size_t* n) const; - - // copies a block of data into a vector - template - std::optional> GetVector(const std::string& name) const; - - // returns true if a block of data by the given name is stored in the asset - bool HasData(const std::string& name) const { - return blocks_.find(name) != blocks_.end(); - } - const std::string& Timestamp() const { return timestamp_; } const std::string& Id() const { return id_; } std::size_t InsertNum() const { return insert_num_; } std::size_t AccessCount() const { return access_count_; } + // pass data in the cache to the given function + void PopulateData(mjCDataFunc fn) const { + fn(data_.get()); + } + private: mjCAsset() = default; @@ -93,12 +70,13 @@ class mjCAsset { void RemoveReference(const std::string& xml_file) { references_.erase(xml_file); } - bool HasReferences() const { return !references_.empty(); } - // replaces data blocks in asset - void ReplaceBlocks( - const std::unordered_map& blocks, - std::size_t nbytes); + void ReplaceData(const mjCAsset& other) { + data_ = other.data_; + size_ = other.size_; + } + + bool HasReferences() const { return !references_.empty(); } void IncrementAccess() { access_count_++; } @@ -110,9 +88,9 @@ class mjCAsset { void SetTimestamp(std::string timestamp) { timestamp_ = timestamp; } // accessors - std::size_t BytesCount() const { return nbytes_; } - const std::unordered_map& Blocks() const { - return blocks_; + std::size_t BytesCount() const { return size_; } + const void* Data() const { + return data_.get(); } const std::set& References() const { return references_; } @@ -120,10 +98,8 @@ class mjCAsset { std::string timestamp_; // opaque timestamp of asset std::size_t insert_num_; // number when asset was inserted std::size_t access_count_ = 0; // incremented when getting 0th block - std::size_t nbytes_ = 0; // how many bytes taken up by the asset - - // the actually data of the asset - std::unordered_map blocks_; + std::size_t size_ = 0; // how many bytes taken up by the asset + std::shared_ptr data_; // actual data of the asset // list of models referencing this asset std::set references_; @@ -160,11 +136,11 @@ class mjCCache { // inserts an asset into the cache, if asset is already in the cache, its data // is updated only if the timestamps disagree - bool Insert(const mjCAsset& asset); - bool Insert(mjCAsset&& asset); + bool Insert(const std::string& modelname, const mjResource *resource, + std::shared_ptr data, std::size_t size); - // returns the asset with the given id, if it exists in the cache - std::optional Get(const std::string& id); + // populate data from the cache into the given function + bool PopulateData(const mjResource* resource, mjCDataFunc fn); // deletes the asset from the cache with the given id void DeleteAsset(const std::string& id); diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 8b954612..77258d8c 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -21,13 +21,13 @@ #include #include #include -#include #include #include #include #include #include +#include #include "user/user_api.h" #ifdef MUJOCO_TINYOBJLOADER_IMPL @@ -69,9 +69,6 @@ extern "C" { #include "qhull_ra.h" } -using std::string; -using std::vector; - // compute triangle area, surface normal, center static double _triangle(double* normal, double* center, const float* v1, const float* v2, const float* v3) { @@ -337,7 +334,41 @@ void mjCMesh::LoadSDF() { delete[] field; } +void mjCMesh::CacheOBJ(mjCCache* cache, const mjResource* resource) { + if (cache == nullptr) return; + // cache mesh data into new mesh object + mjCMesh *mesh = new mjCMesh(); + mesh->vert_ = vert_; + mesh->normal_ = normal_; + mesh->texcoord_ = texcoord_; + mesh->face_ = face_; + mesh->facetexcoord_ = facetexcoord_; + mesh->facenormal_ = facenormal_; + mesh->vertex_index_ = vertex_index_; + mesh->normal_index_ = normal_index_; + mesh->texcoord_index_ = texcoord_index_; + mesh->num_face_vertices_ = num_face_vertices_; + + // calculate estimated size of mesh + std::size_t size = sizeof(mjCMesh) + + (sizeof(float) * vert_.size()) + + (sizeof(float) * normal_.size()) + + (sizeof(float) * texcoord_.size()) + + (sizeof(int) * face_.size()) + + (sizeof(int) * facetexcoord_.size()) + + (sizeof(int) * facenormal_.size()) + + (sizeof(int) * vertex_index_.size()) + + (sizeof(int) * normal_index_.size()) + + (sizeof(int) * texcoord_index_.size()) + + (sizeof(unsigned char) * num_face_vertices_.size()); + + std::shared_ptr cached_data(mesh, +[](const void* data) { + const mjCMesh* mesh = static_cast(data); + delete mesh; + }); + cache->Insert("", resource, cached_data, size); +} // compiler void mjCMesh::Compile(const mjVFS* vfs) { @@ -368,14 +399,19 @@ void mjCMesh::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); + std::string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); mjResource* resource = LoadResource(filename, vfs); try { if (asset_type == "model/stl") { LoadSTL(resource); } else if (asset_type == "model/obj") { - LoadOBJ(resource); + // try loading from cache + mjCCache *cache = reinterpret_cast(mj_globalCache()); + if (!cache || !LoadCachedOBJ(cache, resource)) { + LoadOBJ(resource); + CacheOBJ(cache, resource); + } } else { LoadMSH(resource); } @@ -855,18 +891,6 @@ void mjCMesh::LoadOBJ(mjResource* resource) { tinyobj::ObjReader objReader; const void* bytes = nullptr; - // try loading from cache - mjCCache *cache = reinterpret_cast(mj_globalCache()); - if (cache) { - auto asset = cache->Get(resource->name); - if (asset.has_value() && - !mju_isModifiedResource(resource, asset->Timestamp().c_str())) { - if (LoadCachedOBJ(asset.value())) { - return; - } - } - } - int buffer_sz = mju_readResource(resource, &bytes); if (buffer_sz < 0) { throw mjCError(this, "could not read OBJ file '%s'", resource->name); @@ -933,108 +957,83 @@ void mjCMesh::LoadOBJ(mjResource* resource) { texcoord_[2*i+1] = 1-texcoord_[2*i+1]; } - // try caching asset - if (cache) { - mjCAsset asset("", resource->name, resource->timestamp); + // save some partial data for caching + if (!objReader.GetShapes().empty()) { + const auto& mesh = objReader.GetShapes()[0].mesh; + num_face_vertices_ = mesh.num_face_vertices; - asset.AddVector("vert_", vert_); - asset.AddVector("normal_", normal_); - asset.AddVector("texcoord_", texcoord_); + vertex_index_.reserve(mesh.indices.size()); + normal_index_.reserve(mesh.indices.size()); + texcoord_index_.reserve(mesh.indices.size()); - if (!objReader.GetShapes().empty()) { - const auto& mesh = objReader.GetShapes()[0].mesh; - std::vector vertex_index; - vertex_index.reserve(mesh.indices.size()); - - std::vector normal_index; - normal_index.reserve(mesh.indices.size()); - - std::vector texcoord_index; - texcoord_index.reserve(mesh.indices.size()); - - for (tinyobj::index_t index : mesh.indices) { - vertex_index.push_back(index.vertex_index); - normal_index.push_back(index.normal_index); - texcoord_index.push_back(index.texcoord_index); - } - - asset.AddVector("num_face_vertices", mesh.num_face_vertices); - asset.AddVector("vertex_index", vertex_index); - asset.AddVector("normal_index", normal_index); - asset.AddVector("texcoord_index", texcoord_index); - } else { - asset.AddVector("num_face_vertices", std::vector()); - asset.AddVector("vertex_index", std::vector()); - asset.AddVector("normal_index", std::vector()); - asset.AddVector("texcoord_index", std::vector()); + for (tinyobj::index_t index : mesh.indices) { + vertex_index_.push_back(index.vertex_index); + normal_index_.push_back(index.normal_index); + texcoord_index_.push_back(index.texcoord_index); } - cache->Insert(std::move(asset)); } } // load OBJ from cached asset, return true on success -bool mjCMesh::LoadCachedOBJ(const mjCAsset& asset) { +bool mjCMesh::LoadCachedOBJ(mjCCache *cache, const mjResource* resource) { // check that asset has all data - if (!asset.HasData("vert_") || !asset.HasData("normal_") - || !asset.HasData("texcoord_") || !asset.HasData("num_face_vertices") - || !asset.HasData("vertex_index") || !asset.HasData("normal_index") - || !asset.HasData("texcoord_index")) { + if (!cache->PopulateData(resource, [&](const void* data) { + const mjCMesh* mesh = static_cast(data); + vert_ = mesh->vert_; + normal_ = mesh->normal_; + texcoord_ = mesh->texcoord_; + vertex_index_ = mesh->vertex_index_; + normal_index_ = mesh->normal_index_; + texcoord_index_ = mesh->texcoord_index_; + num_face_vertices_ = mesh->num_face_vertices_; + })) { return false; } - vert_ = asset.GetVector("vert_").value(); - normal_ = asset.GetVector("normal_").value(); - texcoord_ = asset.GetVector("texcoord_").value(); - - vector vertex_index = asset.GetVector("vertex_index").value(); - vector normal_index = asset.GetVector("normal_index").value(); - vector texcoord_index = asset.GetVector("texcoord_index").value(); - vector num_face_vertices = - asset.GetVector("num_face_vertices").value(); bool righthand = (scale[0] * scale[1] * scale[2]) > 0; - for (int face = 0, i = 0; i < vertex_index.size();) { - int nfacevert = num_face_vertices[face]; + for (int face = 0, i = 0; i < vertex_index_.size();) { + int nfacevert = num_face_vertices_[face]; if (nfacevert < 3 || nfacevert > 4) { throw mjCError( this, "only tri or quad meshes are supported for OBJ (file '%s')", - asset.Id().c_str()); + resource->name); } - face_.push_back(vertex_index[i]); - face_.push_back(vertex_index[i + (righthand == 1 ? 1 : 2)]); - face_.push_back(vertex_index[i + (righthand == 1 ? 2 : 1)]); + face_.push_back(vertex_index_[i]); + face_.push_back(vertex_index_[i + (righthand == 1 ? 1 : 2)]); + face_.push_back(vertex_index_[i + (righthand == 1 ? 2 : 1)]); if (!normal_.empty()) { - facenormal_.push_back(normal_index[i]); - facenormal_.push_back(normal_index[i + (righthand == 1 ? 1 : 2)]); - facenormal_.push_back(normal_index[i + (righthand == 1 ? 2 : 1)]); + facenormal_.push_back(normal_index_[i]); + facenormal_.push_back(normal_index_[i + (righthand == 1 ? 1 : 2)]); + facenormal_.push_back(normal_index_[i + (righthand == 1 ? 2 : 1)]); } if (!texcoord_.empty()) { - facetexcoord_.push_back(texcoord_index[i]); - facetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 1 : 2)]); - facetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 2 : 1)]); + facetexcoord_.push_back(texcoord_index_[i]); + facetexcoord_.push_back(texcoord_index_[i + (righthand == 1 ? 1 : 2)]); + facetexcoord_.push_back(texcoord_index_[i + (righthand == 1 ? 2 : 1)]); } if (nfacevert == 4) { - face_.push_back(vertex_index[i]); - face_.push_back(vertex_index[i + (righthand == 1 ? 2 : 3)]); - face_.push_back(vertex_index[i + (righthand == 1 ? 3 : 2)]); + face_.push_back(vertex_index_[i]); + face_.push_back(vertex_index_[i + (righthand == 1 ? 2 : 3)]); + face_.push_back(vertex_index_[i + (righthand == 1 ? 3 : 2)]); if (!normal_.empty()) { - facenormal_.push_back(normal_index[i]); - facenormal_.push_back(normal_index[i + (righthand == 1 ? 1 : 2)]); - facenormal_.push_back(normal_index[i + (righthand == 1 ? 2 : 1)]); + facenormal_.push_back(normal_index_[i]); + facenormal_.push_back(normal_index_[i + (righthand == 1 ? 1 : 2)]); + facenormal_.push_back(normal_index_[i + (righthand == 1 ? 2 : 1)]); } if (!texcoord_.empty()) { - facetexcoord_.push_back(texcoord_index[i]); - facetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 1 : 2)]); - facetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 2 : 1)]); + facetexcoord_.push_back(texcoord_index_[i]); + facetexcoord_.push_back(texcoord_index_[i + (righthand == 1 ? 1 : 2)]); + facetexcoord_.push_back(texcoord_index_[i + (righthand == 1 ? 2 : 1)]); } } i += nfacevert; @@ -2086,12 +2085,12 @@ void mjCSkin::Compile(const mjVFS* vfs) { } // load SKN - string ext = mjuu_getext(file_); + std::string ext = mjuu_getext(file_); if (strcasecmp(ext.c_str(), ".skn")) { throw mjCError(this, "Unknown skin file type: %s", file_.c_str()); } - string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); + std::string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); mjResource* resource = LoadResource(filename, vfs); try { @@ -2152,7 +2151,7 @@ void mjCSkin::Compile(const mjVFS* vfs) { } // set total vertex weights to 0 - vector vw; + std::vector vw; size_t nvert = vert_.size()/3; vw.resize(nvert); fill(vw.begin(), vw.end(), 0.0f); @@ -2520,7 +2519,7 @@ void mjCFlex::Compile(const mjVFS* vfs) { // process elements for (int e=0; e<(int)elem_.size()/(dim+1); e++) { // make sorted copy of element - vector el; + std::vector el; el.assign(elem_.begin()+e*(dim+1), elem_.begin()+(e+1)*(dim+1)); std::sort(el.begin(), el.end()); @@ -2555,7 +2554,7 @@ void mjCFlex::Compile(const mjVFS* vfs) { } // compute global vertex positions - vertxpos = vector (3*nvert); + vertxpos = std::vector (3*nvert); for (int i=0; i < nvert; i++) { // get body id, set vertxpos = body.xpos0 int b = rigid ? vertbodyid[0] : vertbodyid[i]; @@ -2702,17 +2701,17 @@ void mjCFlex::CreateBVH(void) { // create shells and element-vertex collision pairs void mjCFlex::CreateShellPair(void) { - vector> fragspec(nelem*(dim+1)); // [sorted frag vertices, elem, original frag vertices] - vector> connectspec; // [elem1, elem2, common sorted frag vertices] - vector border(nelem, false); // is element on the border - vector borderfrag(nelem*(dim+1), false); // is fragment on the border + std::vector> fragspec(nelem*(dim+1)); // [sorted frag vertices, elem, original frag vertices] + std::vector> connectspec; // [elem1, elem2, common sorted frag vertices] + std::vector border(nelem, false); // is element on the border + std::vector borderfrag(nelem*(dim+1), false); // is fragment on the border // make fragspec for (int e=0; e el; + std::vector el; el.assign(elem_.begin()+n, elem_.begin()+n+dim+1); // line: 2 vertex fragments @@ -2797,13 +2796,13 @@ void mjCFlex::CreateShellPair(void) { int cnt = 1; for (int n=1; n previous = {fragspec[n-1].begin(), fragspec[n-1].begin()+dim}; - vector current = {fragspec[n].begin(), fragspec[n].begin()+dim}; + std::vector previous = {fragspec[n-1].begin(), fragspec[n-1].begin()+dim}; + std::vector current = {fragspec[n].begin(), fragspec[n].begin()+dim}; // same sequential fragments if (previous==current) { // found pair of elements connected by common fragment - vector connect; + std::vector connect; connect.insert(connect.end(), fragspec[n-1][dim]); connect.insert(connect.end(), fragspec[n][dim]); connect.insert(connect.end(), fragspec[n].begin(), fragspec[n].begin()+dim); @@ -2843,10 +2842,10 @@ void mjCFlex::CreateShellPair(void) { // compute elemlayer (distance from border) via value iteration in 3D if (dim<3) { - elemlayer = vector (nelem, 0); + elemlayer = std::vector (nelem, 0); } else { - elemlayer = vector (nelem, nelem+1); // init with greater than max value + elemlayer = std::vector (nelem, nelem+1); // init with greater than max value for (int e=0; e frag = {connect.begin()+2, connect.end()}; + std::vector frag = {connect.begin()+2, connect.end()}; // process both elements for (int ei=0; ei < 2; ei++) { diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 2f506940..5433c515 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -18,10 +18,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -47,10 +49,92 @@ namespace { namespace mju = ::mujoco::util; -using std::string; -using std::vector; + +class PNGImage { + public: + static PNGImage Load(const mjCBase* obj, mjResource* resource, + LodePNGColorType color_type); + int Width() const { return width_; } + int Height() const { return height_; } + uint8_t operator[] (int i) const { return data_[i]; } + std::vector& MoveData() { return data_; } + + private: + std::size_t Size() const { + return data_.size() + (3 * sizeof(int)); + } + + int width_; + int height_; + LodePNGColorType color_type_; + std::vector data_; +}; + +PNGImage PNGImage::Load(const mjCBase* obj, mjResource* resource, + LodePNGColorType color_type) { + PNGImage image; + image.color_type_ = color_type; + mjCCache *cache = reinterpret_cast(mj_globalCache()); + + // try loading from cache + if (cache && cache->PopulateData(resource, [&image](const void* data) { + const PNGImage *cached_image = static_cast(data); + if (cached_image->color_type_ == image.color_type_) { + image = *cached_image; + } + })) { + if (!image.data_.empty()) return image; + } + + // open PNG resource + const unsigned char* buffer; + int nbuffer = mju_readResource(resource, (const void**) &buffer); + + if (nbuffer < 0) { + throw mjCError(obj, "could not read PNG file '%s'", resource->name); + } + + if (!nbuffer) { + throw mjCError(obj, "empty PNG file '%s'", resource->name); + } + + // decode PNG from buffer + unsigned int w, h; + unsigned err = lodepng::decode(image.data_, w, h, + buffer, nbuffer, image.color_type_, 8); + + // check for errors + if (err) { + std::stringstream ss; + ss << "error decoding PNG file '" << resource->name << "': " << lodepng_error_text(err); + throw mjCError(obj, "%s", ss.str().c_str()); + } + + image.width_ = w; + image.height_ = h; + + if (image.width_ <= 0 || image.height_ < 0) { + std::stringstream ss; + ss << "error decoding PNG file '" << resource->name << "': " << "dimensions are invalid"; + throw mjCError(obj, "%s", ss.str().c_str()); + } + + // insert raw image data into cache + if (cache) { + PNGImage *cached_image = new PNGImage(image);; + std::size_t size = image.Size(); + std::shared_ptr cached_data(cached_image, +[](const void* data) { + delete static_cast(data); + }); + cache->Insert("", resource, cached_data, size); + } + + return image; +} + } // namespace + // utiility function for checking size parameters static void checksize(double* size, mjtGeom type, mjCBase* object, const char* name, int id) { // plane: handle infinite @@ -91,92 +175,6 @@ static bool islimited(int limited, const double range[2]) { return false; } - - -// fetches cached image from PNG asset, returns nullopt if not available -static std::optional> -LoadCachedPNG(mjCAsset& asset, unsigned& w, unsigned& h, LodePNGColorType color_type) { - if (!asset.HasData("dims") || !asset.HasData("image")) { - return std::nullopt; - } - - std::vector dims - = asset.GetVector("dims").value(); - auto maybe_cached_image = asset.GetVector("image"); - - if (dims.size() != 3) { - return std::nullopt; - } - - if ((maybe_cached_image->size() != dims[0] * dims[1]) || (color_type != dims[2])) { - return std::nullopt; - } - - w = dims[0]; - h = dims[1]; - - return maybe_cached_image; -} - - - -// decodes PNG images from the given resource -std::vector LoadPNG(const mjCBase* obj, mjResource* resource, - unsigned& w, unsigned& h, LodePNGColorType color_type) { - mjCCache *cache = reinterpret_cast(mj_globalCache()); - - // try loading from cache - if (cache) { - auto asset = cache->Get(resource->name); - if (asset.has_value() && - !mju_isModifiedResource(resource, asset->Timestamp().c_str())) { - auto maybe_cached_image = LoadCachedPNG(asset.value(), w, h, color_type); - if (maybe_cached_image.has_value()) { - return maybe_cached_image.value(); - } - } - } - - // open PNG resource - const unsigned char* buffer; - int buffer_sz = mju_readResource(resource, (const void**) &buffer); - - if (buffer_sz < 0) { - throw mjCError(obj, "could not read PNG file '%s'", resource->name); - } - - if (!buffer_sz) { - throw mjCError(obj, "empty PNG file '%s'", resource->name); - } - - // decode PNG from buffer - std::vector image; - unsigned err = lodepng::decode(image, w, h, buffer, buffer_sz, color_type, 8); - - // check for errors - if (err) { - std::stringstream ss; - ss << "error decoding PNG file '" << resource->name << "': " << lodepng_error_text(err); - throw mjCError(obj, "%s", ss.str().c_str()); - } - - if (!w || !h) { - throw mjCError(obj, "error decoding PNG file '%s': zero dimension", resource->name); - } - - // insert raw image data into cache - if (cache) { - mjCAsset asset("", resource->name, resource->timestamp); - asset.AddVector("dims", std::vector{w, h, static_cast(color_type)}); - asset.AddVector("image", image); - cache->Insert(std::move(asset)); - } - - return image; -} - - - //------------------------- class mjCError implementation ------------------------------------------ // constructor @@ -705,7 +703,7 @@ void mjCBase::NameSpace(const mjCModel* m) { // load resource if found (fallback to OS filesystem) -mjResource* mjCBase::LoadResource(string filename, const mjVFS* vfs) { +mjResource* mjCBase::LoadResource(std::string filename, const mjVFS* vfs) { // try reading from provided VFS mjResource* r = mju_openVfsResource(filename.c_str(), vfs); @@ -1200,7 +1198,7 @@ mjCBase* mjCBody::GetObject(mjtObj type, int id) { // find object by name in given list template -static T* findobject(string name, vector& list) { +static T* findobject(std::string name, std::vector& list) { for (unsigned int i=0; iname == name) { return list[i]; @@ -1213,7 +1211,7 @@ static T* findobject(string name, vector& list) { // recursive find by name -mjCBase* mjCBody::FindObject(mjtObj type, string _name, bool recursive) { +mjCBase* mjCBody::FindObject(mjtObj type, std::string _name, bool recursive) { mjCBase* res = 0; // check self: just in case @@ -1308,7 +1306,7 @@ void mjCBody::GeomFrame(void) { int sz; double com[3] = {0, 0, 0}; double toti[6] = {0, 0, 0, 0, 0, 0}; - vector sel; + std::vector sel; // select geoms based on group sel.clear(); @@ -3103,11 +3101,10 @@ void mjCHField::LoadCustom(mjResource* resource) { // load elevation data from PNG format void mjCHField::LoadPNG(mjResource* resource) { - unsigned w, h; - std::vector image = ::LoadPNG(this, resource, w, h, LCT_GREY); + PNGImage image = PNGImage::Load(this, resource, LCT_GREY); - ncol = w; - nrow = h; + ncol = image.Width(); + nrow = image.Height(); // copy image data over with rows reversed data.reserve(nrow * ncol); @@ -3161,7 +3158,7 @@ void mjCHField::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); + std::string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); mjResource* resource = LoadResource(filename, vfs); try { @@ -3525,7 +3522,10 @@ void mjCTexture::BuiltinCube(void) { void mjCTexture::LoadPNG(mjResource* resource, std::vector& image, unsigned int& w, unsigned int& h) { - image = ::LoadPNG(this, resource, w, h, LCT_RGB); + PNGImage png_image = PNGImage::Load(this, resource, LCT_RGB); + w = png_image.Width(); + h = png_image.Height(); + image = png_image.MoveData(); } @@ -3570,7 +3570,7 @@ void mjCTexture::LoadCustom(mjResource* resource, // load from PNG or custom file, flip if specified -void mjCTexture::LoadFlip(string filename, const mjVFS* vfs, +void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs, std::vector& image, unsigned int& w, unsigned int& h) { std::string asset_type = GetAssetContentType(filename, content_type_); @@ -3646,7 +3646,7 @@ void mjCTexture::LoadFlip(string filename, const mjVFS* vfs, // load 2D -void mjCTexture::Load2D(string filename, const mjVFS* vfs) { +void mjCTexture::Load2D(std::string filename, const mjVFS* vfs) { // load PNG or custom unsigned int w, h; std::vector image; @@ -3669,7 +3669,7 @@ void mjCTexture::Load2D(string filename, const mjVFS* vfs) { // load cube or skybox from single file (repeated or grid) -void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { +void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) { // check gridsize if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) { throw mjCError(this, "gridsize must be non-zero and no more than 12 squares in texture"); @@ -3779,7 +3779,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { } // make filename - string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, cubefiles_[i]); + std::string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, cubefiles_[i]); // load PNG or custom unsigned int w, h; @@ -3877,7 +3877,7 @@ void mjCTexture::Compile(const mjVFS* vfs) { } // make filename - string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, file_); + std::string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, file_); // dispatch if (type==mjTEXTURE_2D) { @@ -4113,7 +4113,7 @@ void mjCPair::ResolveReferences(const mjCModel* m) { // swap if body1 > body2 if (geom1->body->id > geom2->body->id) { - string nametmp = geomname1_; + std::string nametmp = geomname1_; geomname1_ = geomname2_; geomname2_ = nametmp; @@ -4337,7 +4337,7 @@ void mjCBodyPair::ResolveReferences(const mjCModel* m) { // swap if body1 > body2 if (pb1->id > pb2->id) { - string nametmp = bodyname1_; + std::string nametmp = bodyname1_; bodyname1_ = bodyname2_; bodyname2_ = nametmp; @@ -4639,7 +4639,7 @@ void mjCTendon::SetModel(mjCModel* _model) { // add site as wrap object -void mjCTendon::WrapSite(string name, std::string_view info) { +void mjCTendon::WrapSite(std::string name, std::string_view info) { // create wrap object mjCWrap* wrap = new mjCWrap(model, this); wrap->info = info; @@ -4654,7 +4654,7 @@ void mjCTendon::WrapSite(string name, std::string_view info) { // add geom (with side site) as wrap object -void mjCTendon::WrapGeom(string name, string sidesite, std::string_view info) { +void mjCTendon::WrapGeom(std::string name, std::string sidesite, std::string_view info) { // create wrap object mjCWrap* wrap = new mjCWrap(model, this); wrap->info = info; @@ -4670,7 +4670,7 @@ void mjCTendon::WrapGeom(string name, string sidesite, std::string_view info) { // add joint as wrap object -void mjCTendon::WrapJoint(string name, double coef, std::string_view info) { +void mjCTendon::WrapJoint(std::string name, double coef, std::string_view info) { // create wrap object mjCWrap* wrap = new mjCWrap(model, this); wrap->info = info; diff --git a/src/user/user_objects.h b/src/user/user_objects.h index ccd4601c..66a6b7e9 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -846,7 +846,10 @@ class mjCMesh: public mjCMesh_, private mjsMesh { private: void LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format - bool LoadCachedOBJ(const mjCAsset& asset); // load OBJ from cache asset, return true on success + // load OBJ from cache asset, return true on success + bool LoadCachedOBJ(mjCCache *cache, const mjResource* resource); + // put OBJ into asset cache + void CacheOBJ(mjCCache *cache, const mjResource* resource); void LoadSTL(mjResource* resource); // load mesh in STL BIN format void LoadMSH(mjResource* resource); // load mesh in MSH BIN format void LoadSDF(); // generate mesh using marching cubes @@ -864,6 +867,12 @@ class mjCMesh: public mjCMesh_, private mjsMesh { double* center_; // face circumcenter data (3*nface) int* graph_; // convex graph data + // for caching purposes + std::vector vertex_index_; + std::vector normal_index_; + std::vector texcoord_index_; + std::vector num_face_vertices_; + // compute the volume and center-of-mass of the mesh given the face center void ComputeVolume(double CoM[3], mjtGeomInertia type, const double facecen[3], bool exactmeshinertia); diff --git a/test/user/user_cache_test.cc b/test/user/user_cache_test.cc index 99c513ae..e6111bd5 100644 --- a/test/user/user_cache_test.cc +++ b/test/user/user_cache_test.cc @@ -14,19 +14,21 @@ // Tests for user/user_cache.cc -#include +#include +#include #include -#include -#include +#include #include #include +#include #include "test/fixture.h" #include "src/user/user_cache.h" +#include "src/user/user_vfs.h" +#include "src/user/user_resource.h" namespace mujoco { -using ::testing::ElementsAreArray; using ::testing::IsNull; using ::testing::NotNull; using ::testing::StrEq; @@ -36,165 +38,82 @@ using CacheTest = MujocoTest; namespace { constexpr int kMaxSize = 100; // in bytes +constexpr std::string kText = "Hello World"; +constexpr std::string kModel = "myModel"; +constexpr std::string kFile = "hello.txt"; + +void CacheText(mjCCache& cache, const std::string& model, + const std::string& name, const std::string& text) { + mjVFS vfs; + mj_defaultVFS(&vfs); + mj_addBufferVFS(&vfs, name.c_str(), text.data(), text.size()); + mjResource* resource = mju_openVfsResource(name.c_str(), &vfs); + std::shared_ptr data(&text, +[](const void* data) {}); + cache.Insert(model, resource, data, text.size()); + mju_closeResource(resource); + mj_deleteVFS(&vfs); +} + +std::optional +GetCachedText(mjCCache& cache, const std::string& model, + const std::string& name, const std::string& text) { + std::string cached_text; + mjVFS vfs; + mj_defaultVFS(&vfs); + mj_addBufferVFS(&vfs, name.c_str(), text.data(), std::strlen(text.c_str())); + mjResource* resource = mju_openVfsResource(name.c_str(), &vfs); + bool inserted = cache.PopulateData(resource, + [&cached_text](const void* data) { + cached_text = *(static_cast(data)); + }); + mju_closeResource(resource); + mj_deleteVFS(&vfs); + return inserted ? std::optional(cached_text) : std::nullopt; +} + TEST(CacheTest, SizeTest) { mjCCache cache(kMaxSize); EXPECT_EQ(cache.Size(), 0); } -TEST(CacheTest, HasAssetSuccessTest) { +TEST(CacheTest, InsertSuccess) { mjCCache cache(kMaxSize); + CacheText(cache, kModel, kFile, kText); + auto cached_text = GetCachedText(cache, kModel, kFile, kText); - mjCAsset asset("file.xml", "foo.obj", "now"); - cache.Insert(asset); - - EXPECT_THAT(*(cache.HasAsset("foo.obj")), StrEq("now")); + EXPECT_THAT(cached_text.value(), StrEq(kText)); } -TEST(CacheTest, HasAssetFailureTest) { +TEST(CacheTest, InsertFailure) { mjCCache cache(kMaxSize); - - mjCAsset asset("file.xml", "foo.obj", "now"); - cache.Insert(asset); - - EXPECT_THAT(cache.HasAsset("file2.xml"), nullptr); + CacheText(cache, kModel, kFile, kText); + auto cached_text = GetCachedText(cache, kModel, "hello2.txt", kText); + EXPECT_EQ(cached_text, std::nullopt); } -TEST(CacheTest, AddSuccessTest) { +TEST(CacheTest, InsertReplace) { mjCCache cache(kMaxSize); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {1.0, 2.0, 3.0}; + const std::string kUpdatedText = "Goodbye World"; + CacheText(cache, kModel, kFile, kText); + CacheText(cache, kModel, kFile, kUpdatedText); + auto cached_text = GetCachedText(cache, kModel, kFile, kUpdatedText); - mjCAsset asset("file.xml", "foo.obj", "now"); - std::size_t nbytes1 = asset.AddVector("v1", v1); - std::size_t nbytes2 = asset.AddVector("v2", v2); - cache.Insert(asset); - - ASSERT_EQ(nbytes1, 12); - ASSERT_EQ(nbytes2, 24); - ASSERT_EQ(cache.Size(), 36); -} - -TEST(CacheTest, AddFailureTest) { - mjCCache cache(kMaxSize); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {1.0, 2.0, 3.0}; - - mjCAsset asset("file.xml", "foo.obj", "now"); - asset.AddVector("v1", v1); - std::size_t nbytes = asset.AddVector("v1", v2); - cache.Insert(asset); - - ASSERT_EQ(nbytes, 0); - ASSERT_EQ(cache.Size(), 12); -} - -TEST(CacheTest, InsertReplaceTest) { - mjCCache cache(kMaxSize); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {1.0, 2.0, 3.0}; - - mjCAsset asset("file.xml", "foo.obj", "now"); - asset.AddVector("v", v1); - cache.Insert(asset); - - mjCAsset asset2("file.xml", "foo.obj", "nower"); - asset2.AddVector("v", v2); - bool inserted = cache.Insert(asset2); - EXPECT_TRUE(inserted); - - mjCAsset asset3 = *(cache.Get("foo.obj")); - std::vector v3 = asset3.GetVector("v").value(); - EXPECT_THAT(v3, ElementsAreArray(v2)); - - ASSERT_EQ(cache.Size(), 24); -} - -TEST(CacheTest, MoveInsertNewTest) { - mjCCache cache(kMaxSize); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {1.0, 2.0, 3.0}; - - mjCAsset asset("file.xml", "foo.obj", "now"); - std::size_t nbytes1 = asset.AddVector("v1", v1); - std::size_t nbytes2 = asset.AddVector("v2", v2); - cache.Insert(std::move(asset)); - - ASSERT_EQ(nbytes1, 12); - ASSERT_EQ(nbytes2, 24); - ASSERT_EQ(cache.Size(), 36); -} - -TEST(CacheTest, MoveInsertReplaceTest) { - mjCCache cache(kMaxSize); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {1.0, 2.0, 3.0}; - - mjCAsset asset("file.xml", "foo.obj", "now"); - asset.AddVector("v", v1); - cache.Insert(std::move(asset)); - - mjCAsset asset2("file.xml", "foo.obj", "nower"); - asset2.AddVector("v", v2); - bool inserted = cache.Insert(std::move(asset2)); - EXPECT_TRUE(inserted); - - mjCAsset asset3 = *(cache.Get("foo.obj")); - std::vector v3 = asset3.GetVector("v").value(); - EXPECT_THAT(v3, ElementsAreArray(v2)); - - ASSERT_EQ(cache.Size(), 24); -} - -TEST(CacheTest, GetSuccessTest) { - mjCCache cache(kMaxSize); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {4.0, 5.0, 6.0}; - mjCAsset asset("file.xml", "foo.obj", "now"); - asset.AddVector("v1", v1); - asset.AddVector("v2", v2); - cache.Insert(asset); - - mjCAsset asset2 = *(cache.Get("foo.obj")); - - std::vector v3 = asset2.GetVector("v1").value(); - EXPECT_THAT(v3, ElementsAreArray(v1)); - - std::vector v4 = asset2.GetVector("v2").value(); - EXPECT_THAT(v4, ElementsAreArray(v2)); -} - -TEST(CacheTest, GetFailueTest) { - mjCCache cache(kMaxSize); - std::vector v = {1, 2, 3}; - mjCAsset asset("file.xml", "foo.obj", "now"); - asset.AddVector("v", v); - cache.Insert(asset); - - mjCAsset asset2 = *(cache.Get("foo.obj")); - EXPECT_EQ(cache.Get("bar.obj").has_value(), false); - - auto v2 = asset2.GetVector("v2"); - EXPECT_EQ(v2.has_value(), false); + EXPECT_THAT(cached_text.value(), StrEq(kUpdatedText)); } // Trim cache based off of access count -TEST(CacheTest, LimitTest1) { +TEST(CacheTest, Limit1) { mjCCache cache(kMaxSize); EXPECT_THAT(cache.MaxSize(), kMaxSize); - std::vector v = {1, 2, 3}; - mjCAsset asset1 = mjCAsset("file.xml", "foo.obj", "now"); - mjCAsset asset2 = mjCAsset("file.xml", "bar.obj", "now"); - asset1.AddVector("foo.obj", v); - asset2.AddVector("bar.obj", v); - cache.Insert(asset1); - cache.Insert(asset2); + CacheText(cache, "fil.xml", "foo.obj", kText); + CacheText(cache, "fil.xml", "bar.obj", kText); // access asset foo twice, bar one - cache.Get("foo.obj"); - cache.Get("foo.obj"); - cache.Get("bar.obj"); + GetCachedText(cache, "file.xml", "foo.obj", kText); + GetCachedText(cache, "file.xml", "foo.obj", kText); + GetCachedText(cache, "file.xml", "bar.obj", kText); // make max size so cache can hold only one asset cache.SetMaxSize(12); @@ -206,21 +125,16 @@ TEST(CacheTest, LimitTest1) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -// Trim cache based off of insert order -TEST(CacheTest, LimitTest2) { - mjCCache cache(kMaxSize); - std::vector v = {1, 2, 3}; - mjCAsset asset1("file.xml", "foo.obj", "now"); - mjCAsset asset2("file.xml", "bar.obj", "now"); - asset1.AddVector("v", v); - asset2.AddVector("v", v); - cache.Insert(asset1); - cache.Insert(asset2); +// Trim cache based off of insert order +TEST(CacheTest, Limit2) { + mjCCache cache(kMaxSize); + CacheText(cache, "file.xml", "foo.obj", kText); + CacheText(cache, "file.xml", "bar.obj", kText); // get each asset once - mjCAsset asset3 = *(cache.Get("foo.obj")); - mjCAsset asset4 = *(cache.Get("bar.obj")); + GetCachedText(cache, "file.xml", "foo.obj", kText); + GetCachedText(cache, "file.xml", "bar.obj", kText); // make max size so cache can hold only one asset cache.SetMaxSize(12); @@ -233,17 +147,10 @@ TEST(CacheTest, LimitTest2) { } // stress test with large asset -TEST(CacheTest, LimitTest3) { +TEST(CacheTest, Limit3) { mjCCache cache(12); - std::vector v1 = {1, 2, 3}; - std::vector v2 = {1, 2, 3, 4, 5}; - mjCAsset asset1("file.xml", "foo.obj", "now"); - mjCAsset asset2("file.xml", "bar.obj", "now"); - asset1.AddVector("v", v1); - asset2.AddVector("v", v2); - - cache.Insert(std::move(asset1)); - cache.Insert(std::move(asset2)); + CacheText(cache, "file.xml", "foo.obj", kText); + CacheText(cache, "file.xml", "bar.obj", kText); // foo should still be in cache EXPECT_THAT(cache.HasAsset("foo.obj"), NotNull()); @@ -252,16 +159,10 @@ TEST(CacheTest, LimitTest3) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -TEST(CacheTest, LimitTest4) { +TEST(CacheTest, Limit4) { mjCCache cache(12); - std::vector v = {1, 2, 3}; - mjCAsset asset1("file.xml", "foo.obj", "now"); - mjCAsset asset2("file.xml", "bar.obj", "now"); - asset1.AddVector("v", v); - asset2.AddVector("v", v); - - cache.Insert(std::move(asset1)); - cache.Insert(std::move(asset2)); + CacheText(cache, "file.xml", "foo.obj", kText); + CacheText(cache, "file.xml", "bar.obj", kText); EXPECT_THAT(cache.HasAsset("foo.obj"), NotNull()); @@ -269,12 +170,10 @@ TEST(CacheTest, LimitTest4) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -TEST(CacheTest, ResetAllTest) { +TEST(CacheTest, ResetAll) { mjCCache cache(kMaxSize); - mjCAsset asset1("file1.xml", "foo.obj", "now"); - mjCAsset asset2("file2.xml", "bar.obj", "now"); - cache.Insert(asset1); - cache.Insert(asset2); + CacheText(cache, "file1.xml", "foo.obj", kText); + CacheText(cache, "file2.xml", "bar.obj", kText); EXPECT_THAT(cache.HasAsset("foo.obj"), NotNull()); EXPECT_THAT(cache.HasAsset("bar.obj"), NotNull()); @@ -285,14 +184,11 @@ TEST(CacheTest, ResetAllTest) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -TEST(CacheTest, ResetModelTest1) { +TEST(CacheTest, ResetModel1) { mjCCache cache(kMaxSize); - mjCAsset asset("file1.xml", "foo.obj", "now"); - mjCAsset asset2("file1.xml", "bar.obj", "now"); - mjCAsset asset3("file2.xml", "bar.obj", "now"); - cache.Insert(asset); - cache.Insert(asset2); - cache.Insert(asset3); + CacheText(cache, "file1.xml", "foo.obj", kText); + CacheText(cache, "file1.xml", "bar.obj", kText); + CacheText(cache, "file2.xml", "bar.obj", kText); cache.Reset("file2.xml"); @@ -300,14 +196,11 @@ TEST(CacheTest, ResetModelTest1) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -TEST(CacheTest, ResetModelTest2) { +TEST(CacheTest, ResetModel2) { mjCCache cache(kMaxSize); - mjCAsset asset("file1.xml", "foo.obj", "now"); - mjCAsset asset2("file2.xml", "foo.obj", "now"); - mjCAsset asset3("file2.xml", "bar.obj", "now"); - cache.Insert(asset); - cache.Insert(asset2); - cache.Insert(asset3); + CacheText(cache, "file1.xml", "foo.obj", kText); + CacheText(cache, "file2.xml", "foo.obj", kText); + CacheText(cache, "file2.xml", "bar.obj", kText); cache.Reset("file2.xml"); @@ -315,14 +208,11 @@ TEST(CacheTest, ResetModelTest2) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -TEST(CacheTest, RemoveModelTest1) { +TEST(CacheTest, RemoveModel1) { mjCCache cache(kMaxSize); - mjCAsset asset("file1.xml", "foo.obj", "now"); - mjCAsset asset2("file1.xml", "bar.obj", "now"); - mjCAsset asset3("file2.xml", "bar.obj", "now"); - cache.Insert(asset); - cache.Insert(asset2); - cache.Insert(asset3); + CacheText(cache, "file1.xml", "foo.obj", kText); + CacheText(cache, "file1.xml", "bar.obj", kText); + CacheText(cache, "file2.xml", "bar.obj", kText); cache.RemoveModel("file2.xml"); @@ -330,12 +220,10 @@ TEST(CacheTest, RemoveModelTest1) { EXPECT_THAT(cache.HasAsset("bar.obj"), NotNull()); } -TEST(CacheTest, RemoveModelTest2) { +TEST(CacheTest, RemoveModel2) { mjCCache cache(kMaxSize); - mjCAsset asset("file1.xml", "foo.obj", "now"); - mjCAsset asset2("file2.xml", "bar.obj", "now"); - cache.Insert(asset); - cache.Insert(asset2); + CacheText(cache, "file1.xml", "foo.obj", kText); + CacheText(cache, "file2.xml", "bar.obj", kText); cache.Reset("file2.xml"); @@ -343,20 +231,18 @@ TEST(CacheTest, RemoveModelTest2) { EXPECT_THAT(cache.HasAsset("bar.obj"), IsNull()); } -TEST(CacheTest, DeleteAssetSuccessTest) { +TEST(CacheTest, DeleteAssetSuccess) { mjCCache cache(kMaxSize); - mjCAsset asset("file1.xml", "foo.obj", "now"); - cache.Insert(asset); + CacheText(cache, "file.xml", "foo.obj", kText); cache.DeleteAsset("foo.obj"); EXPECT_THAT(cache.HasAsset("foo.obj"), IsNull()); } -TEST(CacheTest, DeleteAssetFailureTest) { +TEST(CacheTest, DeleteAssetFailure) { mjCCache cache(kMaxSize); - mjCAsset asset("file.xml", "foo.obj", "now"); - cache.Insert(asset); + CacheText(cache, "file.xml", "foo.obj", kText); cache.DeleteAsset("bar.obj");