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