1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /// @file points/AttributeArrayString.cc
5 
6 #include "AttributeArrayString.h"
7 
8 #include <openvdb/Metadata.h>
9 #include <openvdb/MetaMap.h>
10 
11 #include <tbb/parallel_sort.h>
12 
13 #include <string>
14 
15 namespace openvdb {
16 OPENVDB_USE_VERSION_NAMESPACE
17 namespace OPENVDB_VERSION_NAME {
18 namespace points {
19 
20 
21 namespace {
22 
getStringKey(const Index index)23     Name getStringKey(const Index index)
24     {
25         return "string:" + std::to_string(index - 1);
26     }
27 
28 } // namespace
29 
30 
31 ////////////////////////////////////////
32 
33 
34 // StringMetaCache implementation
35 
36 
StringMetaCache(const MetaMap & metadata)37 StringMetaCache::StringMetaCache(const MetaMap& metadata)
38 {
39     this->reset(metadata);
40 }
41 
42 
insert(const Name & key,Index index)43 void StringMetaCache::insert(const Name& key, Index index)
44 {
45     mCache[key] = index;
46 }
47 
48 
reset(const MetaMap & metadata)49 void StringMetaCache::reset(const MetaMap& metadata)
50 {
51     mCache.clear();
52 
53     // populate the cache
54 
55     for (auto it = metadata.beginMeta(), itEnd = metadata.endMeta(); it != itEnd; ++it) {
56         const Name& key = it->first;
57         const Metadata::Ptr& meta = it->second;
58 
59         // attempt to cast metadata to StringMetadata
60         const StringMetadata* stringMeta = dynamic_cast<StringMetadata*>(meta.get());
61         if (!stringMeta)                            continue;
62 
63         // string attribute metadata must have a key that starts "string:"
64         if (key.compare(0, 7, "string:") != 0)      continue;
65 
66         // remove "string:" and cast to Index
67         Index index = 1 + static_cast<Index>(
68             std::stoul(key.substr(7, key.size() - 7)));
69 
70         // add to the cache
71         this->insert(stringMeta->value(), index);
72     }
73 }
74 
75 
76 ////////////////////////////////////////
77 
78 // StringMetaInserter implementation
79 
80 
StringMetaInserter(MetaMap & metadata)81 StringMetaInserter::StringMetaInserter(MetaMap& metadata)
82     : mMetadata(metadata)
83     , mIdBlocks()
84     , mCache()
85 {
86     // populate the cache
87     resetCache();
88 }
89 
90 
hasKey(const Name & key) const91 bool StringMetaInserter::hasKey(const Name& key) const
92 {
93     return mCache.map().find(key) != mCache.map().end();
94 }
95 
96 
hasIndex(Index index) const97 bool StringMetaInserter::hasIndex(Index index) const
98 {
99     return bool(mMetadata[getStringKey(index)]);
100 }
101 
102 
insert(const Name & name,Index hint)103 Index StringMetaInserter::insert(const Name& name, Index hint)
104 {
105     using IterT = IndexPairArray::iterator;
106 
107     // if name already exists, return the index
108 
109     const auto& cacheMap = mCache.map();
110     auto it = cacheMap.find(name);
111     if (it != cacheMap.end()) {
112         return it->second;
113     }
114 
115     Index index = 1;
116 
117     Name hintKey;
118     bool canUseHint = false;
119 
120     // hint must be non-zero to have been requested
121 
122     if (hint > Index(0)) {
123         hintKey = getStringKey(hint);
124         // check if hint is already in use
125         if (!bool(mMetadata[hintKey])) {
126             canUseHint = true;
127             index = hint;
128         }
129     }
130 
131     // look through the id blocks for hint or index
132 
133     IterT iter = mIdBlocks.begin();
134     for (; iter != mIdBlocks.end(); ++iter) {
135         const Index start = iter->first;
136         const Index end = start + iter->second;
137 
138         if (index < start || index >= end) break;
139         if (!canUseHint)    index = end;
140     }
141 
142     // index now holds the next valid index. if it's 1 (the beginning
143     // iterator) no initial block exists - add it
144 
145     IterT prevIter;
146     if (iter == mIdBlocks.begin()) {
147         prevIter = mIdBlocks.emplace(iter, 1, 1);
148         iter = std::next(prevIter);
149     }
150     else {
151         // accumulate the id block size where the next index is going
152         prevIter = std::prev(iter);
153         prevIter->second++;
154     }
155 
156     // see if this block and the next block can be compacted
157 
158     if (iter != mIdBlocks.end() &&
159         prevIter->second + 1 == iter->first) {
160         prevIter->second += iter->second;
161         mIdBlocks.erase(iter);
162     }
163 
164     // insert into metadata
165 
166     const Name key = getStringKey(index);
167     mMetadata.insertMeta(key, StringMetadata(name));
168 
169     // update the cache
170 
171     mCache.insert(name, index);
172 
173     return index;
174 }
175 
176 
resetCache()177 void StringMetaInserter::resetCache()
178 {
179     mCache.reset(mMetadata);
180     mIdBlocks.clear();
181 
182     std::vector<Index> stringIndices;
183     stringIndices.reserve(mCache.size());
184 
185     if (mCache.empty()) return;
186 
187     const auto& cacheMap = mCache.map();
188 
189     for (auto it = cacheMap.cbegin(); it != cacheMap.cend(); ++it) {
190         const Index index = it->second;
191 
192         stringIndices.emplace_back(index);
193     }
194 
195     tbb::parallel_sort(stringIndices.begin(), stringIndices.end());
196 
197     // bucket string indices
198 
199     Index key = stringIndices.front();
200     Index size = 0;
201 
202     // For each id, see if it's adjacent id is sequentially increasing and continue to
203     // track how many are until we find a value that isn't. Store the start and length
204     // of each of these blocks. For example, the following container could be created
205     // consisting of 3 elements:
206     //   key  ->  size
207     //   -------------
208     //   7    ->  1000  (values 7->1007)
209     //   1020 ->  5     (values 1020->1025)
210     //   2013 ->  30    (values 2013->2043)
211     // Note that the end value is exclusive (values 1007, 1025 and 2043 do not exist
212     // given the above example)
213 
214     for (const Index id : stringIndices) {
215         if (key + size != id) {
216             assert(size > 0);
217             mIdBlocks.emplace_back(key, size);
218             size = 0;
219             key = id;
220         }
221         ++size;
222     }
223 
224     // add the last block
225     mIdBlocks.emplace_back(key, size);
226 }
227 
228 
229 ////////////////////////////////////////
230 
231 // StringAttributeHandle implementation
232 
233 
234 StringAttributeHandle::Ptr
create(const AttributeArray & array,const MetaMap & metadata,const bool preserveCompression)235 StringAttributeHandle::create(const AttributeArray& array, const MetaMap& metadata, const bool preserveCompression)
236 {
237     return std::make_shared<StringAttributeHandle>(array, metadata, preserveCompression);
238 }
239 
240 
StringAttributeHandle(const AttributeArray & array,const MetaMap & metadata,const bool preserveCompression)241 StringAttributeHandle::StringAttributeHandle(const AttributeArray& array,
242                                              const MetaMap& metadata,
243                                              const bool preserveCompression)
244         : mHandle(array, preserveCompression)
245         , mMetadata(metadata)
246 {
247     if (!isString(array)) {
248         OPENVDB_THROW(TypeError, "Cannot create a StringAttributeHandle for an attribute array that is not a string.");
249     }
250 }
251 
252 
get(Index n,Index m) const253 Name StringAttributeHandle::get(Index n, Index m) const
254 {
255     Name name;
256     this->get(name, n, m);
257     return name;
258 }
259 
260 
get(Name & name,Index n,Index m) const261 void StringAttributeHandle::get(Name& name, Index n, Index m) const
262 {
263     Index index = mHandle.get(n, m);
264 
265     // index zero is reserved for an empty string
266 
267     if (index == 0) {
268         name = "";
269         return;
270     }
271 
272     const Name key = getStringKey(index);
273 
274     // key is assumed to exist in metadata
275 
276     openvdb::StringMetadata::ConstPtr meta = mMetadata.getMetadata<StringMetadata>(key);
277 
278     if (!meta) {
279         OPENVDB_THROW(LookupError, "String attribute cannot be found with index - \"" << index << "\".");
280     }
281 
282     name = meta->value();
283 }
284 
array() const285 const AttributeArray& StringAttributeHandle::array() const
286 {
287     return mHandle.array();
288 }
289 
290 
291 ////////////////////////////////////////
292 
293 // StringAttributeWriteHandle implementation
294 
295 StringAttributeWriteHandle::Ptr
create(AttributeArray & array,const MetaMap & metadata,const bool expand)296 StringAttributeWriteHandle::create(AttributeArray& array, const MetaMap& metadata, const bool expand)
297 {
298     return std::make_shared<StringAttributeWriteHandle>(array, metadata, expand);
299 }
300 
301 
StringAttributeWriteHandle(AttributeArray & array,const MetaMap & metadata,const bool expand)302 StringAttributeWriteHandle::StringAttributeWriteHandle(AttributeArray& array,
303                                                        const MetaMap& metadata,
304                                                        const bool expand)
305     : StringAttributeHandle(array, metadata, /*preserveCompression=*/ false)
306     , mWriteHandle(array, expand)
307 {
308     // populate the cache
309     resetCache();
310 }
311 
312 
expand(bool fill)313 void StringAttributeWriteHandle::expand(bool fill)
314 {
315     mWriteHandle.expand(fill);
316 }
317 
318 
collapse()319 void StringAttributeWriteHandle::collapse()
320 {
321     // zero is used for an empty string
322     mWriteHandle.collapse(0);
323 }
324 
325 
collapse(const Name & name)326 void StringAttributeWriteHandle::collapse(const Name& name)
327 {
328     Index index = getIndex(name);
329     mWriteHandle.collapse(index);
330 }
331 
332 
compact()333 bool StringAttributeWriteHandle::compact()
334 {
335     return mWriteHandle.compact();
336 }
337 
338 
fill(const Name & name)339 void StringAttributeWriteHandle::fill(const Name& name)
340 {
341     Index index = getIndex(name);
342     mWriteHandle.fill(index);
343 }
344 
345 
set(Index n,const Name & name)346 void StringAttributeWriteHandle::set(Index n, const Name& name)
347 {
348     Index index = getIndex(name);
349     mWriteHandle.set(n, /*stride*/0, index);
350 }
351 
352 
set(Index n,Index m,const Name & name)353 void StringAttributeWriteHandle::set(Index n, Index m, const Name& name)
354 {
355     Index index = getIndex(name);
356     mWriteHandle.set(n, m, index);
357 }
358 
359 
resetCache()360 void StringAttributeWriteHandle::resetCache()
361 {
362     mCache.reset(mMetadata);
363 }
364 
365 
array()366 AttributeArray& StringAttributeWriteHandle::array()
367 {
368     return mWriteHandle.array();
369 }
370 
371 
contains(const Name & name) const372 bool StringAttributeWriteHandle::contains(const Name& name) const
373 {
374     // empty strings always have an index at index zero
375     if (name.empty())   return true;
376     const auto& cacheMap = mCache.map();
377     return cacheMap.find(name) != cacheMap.end();
378 }
379 
380 
getIndex(const Name & name) const381 Index StringAttributeWriteHandle::getIndex(const Name& name) const
382 {
383     // zero used for an empty string
384     if (name.empty())   return Index(0);
385 
386     const auto& cacheMap = mCache.map();
387     auto it = cacheMap.find(name);
388 
389     if (it == cacheMap.end()) {
390         OPENVDB_THROW(LookupError, "String does not exist in Metadata, insert it and reset the cache - \"" << name << "\".");
391     }
392 
393     return it->second;
394 }
395 
396 
397 ////////////////////////////////////////
398 
399 
400 } // namespace points
401 } // namespace OPENVDB_VERSION_NAME
402 } // namespace openvdb
403