Line | Branch | Exec | Source |
---|---|---|---|
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 | |||
23 | 1510193 | Name getStringKey(const Index index) | |
24 | { | ||
25 |
1/2✓ Branch 2 taken 1510193 times.
✗ Branch 3 not taken.
|
3020386 | return "string:" + std::to_string(index - 1); |
26 | } | ||
27 | |||
28 | } // namespace | ||
29 | |||
30 | |||
31 | //////////////////////////////////////// | ||
32 | |||
33 | |||
34 | // StringMetaCache implementation | ||
35 | |||
36 | |||
37 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | StringMetaCache::StringMetaCache(const MetaMap& metadata) |
38 | { | ||
39 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | this->reset(metadata); |
40 | 1 | } | |
41 | |||
42 | |||
43 | 36895 | void StringMetaCache::insert(const Name& key, Index index) | |
44 | { | ||
45 | 36895 | mCache[key] = index; | |
46 | 36895 | } | |
47 | |||
48 | |||
49 | 4263 | void StringMetaCache::reset(const MetaMap& metadata) | |
50 | { | ||
51 | mCache.clear(); | ||
52 | |||
53 | // populate the cache | ||
54 | |||
55 |
2/2✓ Branch 0 taken 26770 times.
✓ Branch 1 taken 4263 times.
|
31033 | for (auto it = metadata.beginMeta(), itEnd = metadata.endMeta(); it != itEnd; ++it) { |
56 |
1/2✓ Branch 0 taken 26770 times.
✗ Branch 1 not taken.
|
26770 | const Name& key = it->first; |
57 | const Metadata::Ptr& meta = it->second; | ||
58 | |||
59 | // attempt to cast metadata to StringMetadata | ||
60 |
1/2✓ Branch 0 taken 26770 times.
✗ Branch 1 not taken.
|
26770 | const StringMetadata* stringMeta = dynamic_cast<StringMetadata*>(meta.get()); |
61 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26769 times.
|
26770 | if (!stringMeta) continue; |
62 | |||
63 | // string attribute metadata must have a key that starts "string:" | ||
64 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 26768 times.
|
26769 | if (key.compare(0, 7, "string:") != 0) continue; |
65 | |||
66 | // remove "string:" and cast to Index | ||
67 | 26768 | Index index = 1 + static_cast<Index>( | |
68 | 53536 | std::stoul(key.substr(7, key.size() - 7))); | |
69 | |||
70 | // add to the cache | ||
71 | 26768 | this->insert(stringMeta->value(), index); | |
72 | } | ||
73 | 4263 | } | |
74 | |||
75 | |||
76 | //////////////////////////////////////// | ||
77 | |||
78 | // StringMetaInserter implementation | ||
79 | |||
80 | |||
81 | 845 | StringMetaInserter::StringMetaInserter(MetaMap& metadata) | |
82 | : mMetadata(metadata) | ||
83 | , mIdBlocks() | ||
84 |
1/2✓ Branch 2 taken 845 times.
✗ Branch 3 not taken.
|
845 | , mCache() |
85 | { | ||
86 | // populate the cache | ||
87 |
1/2✓ Branch 1 taken 845 times.
✗ Branch 2 not taken.
|
845 | resetCache(); |
88 | 845 | } | |
89 | |||
90 | |||
91 | 3 | bool StringMetaInserter::hasKey(const Name& key) const | |
92 | { | ||
93 | 3 | return mCache.map().find(key) != mCache.map().end(); | |
94 | } | ||
95 | |||
96 | |||
97 | 3 | bool StringMetaInserter::hasIndex(Index index) const | |
98 | { | ||
99 | 6 | return bool(mMetadata[getStringKey(index)]); | |
100 | } | ||
101 | |||
102 | |||
103 | 1020315 | 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 |
2/2✓ Branch 0 taken 1010189 times.
✓ Branch 1 taken 10126 times.
|
1020315 | auto it = cacheMap.find(name); |
111 |
2/2✓ Branch 0 taken 1010189 times.
✓ Branch 1 taken 10126 times.
|
1020315 | if (it != cacheMap.end()) { |
112 | 1010189 | 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 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10122 times.
|
10126 | if (hint > Index(0)) { |
123 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | hintKey = getStringKey(hint); |
124 | // check if hint is already in use | ||
125 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
|
8 | 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 |
2/2✓ Branch 0 taken 10073 times.
✓ Branch 1 taken 10121 times.
|
20194 | for (; iter != mIdBlocks.end(); ++iter) { |
135 | 10073 | const Index start = iter->first; | |
136 | 10073 | const Index end = start + iter->second; | |
137 | |||
138 |
2/2✓ Branch 0 taken 10068 times.
✓ Branch 1 taken 5 times.
|
10073 | if (index < start || index >= end) break; |
139 |
1/2✓ Branch 0 taken 10068 times.
✗ Branch 1 not taken.
|
10068 | 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 |
2/2✓ Branch 0 taken 59 times.
✓ Branch 1 taken 10067 times.
|
10126 | if (iter == mIdBlocks.begin()) { |
147 |
1/2✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
|
59 | prevIter = mIdBlocks.emplace(iter, 1, 1); |
148 | 59 | iter = std::next(prevIter); | |
149 | } | ||
150 | else { | ||
151 | // accumulate the id block size where the next index is going | ||
152 | 10067 | prevIter = std::prev(iter); | |
153 | 10067 | prevIter->second++; | |
154 | } | ||
155 | |||
156 | // see if this block and the next block can be compacted | ||
157 | |||
158 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10121 times.
|
10126 | if (iter != mIdBlocks.end() && |
159 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
|
5 | prevIter->second + 1 == iter->first) { |
160 | 2 | prevIter->second += iter->second; | |
161 | 2 | mIdBlocks.erase(iter); | |
162 | } | ||
163 | |||
164 | // insert into metadata | ||
165 | |||
166 |
1/2✓ Branch 1 taken 10126 times.
✗ Branch 2 not taken.
|
10126 | const Name key = getStringKey(index); |
167 |
2/4✓ Branch 1 taken 10126 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10126 times.
✗ Branch 5 not taken.
|
20252 | mMetadata.insertMeta(key, StringMetadata(name)); |
168 | |||
169 | // update the cache | ||
170 | |||
171 |
1/2✓ Branch 1 taken 10126 times.
✗ Branch 2 not taken.
|
10126 | mCache.insert(name, index); |
172 | |||
173 | return index; | ||
174 | } | ||
175 | |||
176 | |||
177 | 850 | void StringMetaInserter::resetCache() | |
178 | { | ||
179 | 850 | mCache.reset(mMetadata); | |
180 | 850 | mIdBlocks.clear(); | |
181 | |||
182 | std::vector<Index> stringIndices; | ||
183 |
1/2✓ Branch 1 taken 850 times.
✗ Branch 2 not taken.
|
850 | stringIndices.reserve(mCache.size()); |
184 | |||
185 |
2/2✓ Branch 0 taken 779 times.
✓ Branch 1 taken 71 times.
|
850 | if (mCache.empty()) return; |
186 | |||
187 | const auto& cacheMap = mCache.map(); | ||
188 | |||
189 |
2/2✓ Branch 0 taken 10117 times.
✓ Branch 1 taken 71 times.
|
10188 | for (auto it = cacheMap.cbegin(); it != cacheMap.cend(); ++it) { |
190 | 10117 | const Index index = it->second; | |
191 | |||
192 |
1/2✓ Branch 1 taken 10117 times.
✗ Branch 2 not taken.
|
10117 | stringIndices.emplace_back(index); |
193 | } | ||
194 | |||
195 | 71 | tbb::parallel_sort(stringIndices.begin(), stringIndices.end()); | |
196 | |||
197 | // bucket string indices | ||
198 | |||
199 | 71 | Index key = stringIndices.front(); | |
200 | 71 | 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 |
2/2✓ Branch 0 taken 10117 times.
✓ Branch 1 taken 71 times.
|
10188 | for (const Index id : stringIndices) { |
215 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10116 times.
|
10117 | if (key + size != id) { |
216 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | assert(size > 0); |
217 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | mIdBlocks.emplace_back(key, size); |
218 | 1 | size = 0; | |
219 | 1 | key = id; | |
220 | } | ||
221 | 10117 | ++size; | |
222 | } | ||
223 | |||
224 | // add the last block | ||
225 |
1/2✓ Branch 1 taken 71 times.
✗ Branch 2 not taken.
|
71 | mIdBlocks.emplace_back(key, size); |
226 | } | ||
227 | |||
228 | |||
229 | //////////////////////////////////////// | ||
230 | |||
231 | // StringAttributeHandle implementation | ||
232 | |||
233 | |||
234 | StringAttributeHandle::Ptr | ||
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 | |||
241 | 9233 | StringAttributeHandle::StringAttributeHandle(const AttributeArray& array, | |
242 | const MetaMap& metadata, | ||
243 | 9233 | const bool preserveCompression) | |
244 | : mHandle(array, preserveCompression) | ||
245 |
1/2✓ Branch 2 taken 9232 times.
✗ Branch 3 not taken.
|
9233 | , mMetadata(metadata) |
246 | { | ||
247 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9232 times.
|
9232 | if (!isString(array)) { |
248 | ✗ | OPENVDB_THROW(TypeError, "Cannot create a StringAttributeHandle for an attribute array that is not a string."); | |
249 | } | ||
250 | 9232 | } | |
251 | |||
252 | |||
253 |
2/2✓ Branch 1 taken 1500044 times.
✓ Branch 2 taken 1 times.
|
1500045 | Name StringAttributeHandle::get(Index n, Index m) const |
254 | { | ||
255 | Name name; | ||
256 |
2/2✓ Branch 1 taken 1500044 times.
✓ Branch 2 taken 1 times.
|
1500045 | this->get(name, n, m); |
257 | 1500044 | return name; | |
258 | } | ||
259 | |||
260 | |||
261 | 1500272 | void StringAttributeHandle::get(Name& name, Index n, Index m) const | |
262 | { | ||
263 | 1500272 | Index index = mHandle.get(n, m); | |
264 | |||
265 | // index zero is reserved for an empty string | ||
266 | |||
267 |
2/2✓ Branch 0 taken 212 times.
✓ Branch 1 taken 1500060 times.
|
1500272 | if (index == 0) { |
268 | 212 | name = ""; | |
269 | 212 | return; | |
270 | } | ||
271 | |||
272 | 1500060 | const Name key = getStringKey(index); | |
273 | |||
274 | // key is assumed to exist in metadata | ||
275 | |||
276 |
1/2✓ Branch 1 taken 1500060 times.
✗ Branch 2 not taken.
|
1500060 | openvdb::StringMetadata::ConstPtr meta = mMetadata.getMetadata<StringMetadata>(key); |
277 | |||
278 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1500059 times.
|
1500060 | if (!meta) { |
279 |
2/6✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
5 | OPENVDB_THROW(LookupError, "String attribute cannot be found with index - \"" << index << "\"."); |
280 | } | ||
281 | |||
282 | name = meta->value(); | ||
283 | } | ||
284 | |||
285 | 225 | const AttributeArray& StringAttributeHandle::array() const | |
286 | { | ||
287 | 225 | return mHandle.array(); | |
288 | } | ||
289 | |||
290 | |||
291 | //////////////////////////////////////// | ||
292 | |||
293 | // StringAttributeWriteHandle implementation | ||
294 | |||
295 | StringAttributeWriteHandle::Ptr | ||
296 | 148 | StringAttributeWriteHandle::create(AttributeArray& array, const MetaMap& metadata, const bool expand) | |
297 | { | ||
298 | 148 | return std::make_shared<StringAttributeWriteHandle>(array, metadata, expand); | |
299 | } | ||
300 | |||
301 | |||
302 | 3409 | StringAttributeWriteHandle::StringAttributeWriteHandle(AttributeArray& array, | |
303 | const MetaMap& metadata, | ||
304 | 3409 | const bool expand) | |
305 | : StringAttributeHandle(array, metadata, /*preserveCompression=*/ false) | ||
306 |
1/2✓ Branch 2 taken 3409 times.
✗ Branch 3 not taken.
|
3409 | , mWriteHandle(array, expand) |
307 | { | ||
308 | // populate the cache | ||
309 |
1/2✓ Branch 1 taken 3409 times.
✗ Branch 2 not taken.
|
3409 | resetCache(); |
310 | 3409 | } | |
311 | |||
312 | |||
313 | 2 | void StringAttributeWriteHandle::expand(bool fill) | |
314 | { | ||
315 | 2 | mWriteHandle.expand(fill); | |
316 | 2 | } | |
317 | |||
318 | |||
319 | 1 | void StringAttributeWriteHandle::collapse() | |
320 | { | ||
321 | // zero is used for an empty string | ||
322 | 1 | mWriteHandle.collapse(0); | |
323 | 1 | } | |
324 | |||
325 | |||
326 | 186 | void StringAttributeWriteHandle::collapse(const Name& name) | |
327 | { | ||
328 | 186 | Index index = getIndex(name); | |
329 | 186 | mWriteHandle.collapse(index); | |
330 | 186 | } | |
331 | |||
332 | |||
333 | 2909 | bool StringAttributeWriteHandle::compact() | |
334 | { | ||
335 | 2909 | return mWriteHandle.compact(); | |
336 | } | ||
337 | |||
338 | |||
339 | 1 | void StringAttributeWriteHandle::fill(const Name& name) | |
340 | { | ||
341 | 1 | Index index = getIndex(name); | |
342 | 1 | mWriteHandle.fill(index); | |
343 | 1 | } | |
344 | |||
345 | |||
346 | 220 | void StringAttributeWriteHandle::set(Index n, const Name& name) | |
347 | { | ||
348 | 220 | Index index = getIndex(name); | |
349 | 219 | mWriteHandle.set(n, /*stride*/0, index); | |
350 | 219 | } | |
351 | |||
352 | |||
353 | 1000021 | void StringAttributeWriteHandle::set(Index n, Index m, const Name& name) | |
354 | { | ||
355 | 1000021 | Index index = getIndex(name); | |
356 | 1000021 | mWriteHandle.set(n, m, index); | |
357 | 1000021 | } | |
358 | |||
359 | |||
360 | 3411 | void StringAttributeWriteHandle::resetCache() | |
361 | { | ||
362 | 3411 | mCache.reset(mMetadata); | |
363 | 3411 | } | |
364 | |||
365 | |||
366 | 210 | AttributeArray& StringAttributeWriteHandle::array() | |
367 | { | ||
368 | 210 | return mWriteHandle.array(); | |
369 | } | ||
370 | |||
371 | |||
372 |
2/2✓ Branch 0 taken 5196 times.
✓ Branch 1 taken 22 times.
|
5218 | bool StringAttributeWriteHandle::contains(const Name& name) const |
373 | { | ||
374 | // empty strings always have an index at index zero | ||
375 |
2/2✓ Branch 0 taken 5196 times.
✓ Branch 1 taken 22 times.
|
5218 | if (name.empty()) return true; |
376 | const auto& cacheMap = mCache.map(); | ||
377 | 5196 | return cacheMap.find(name) != cacheMap.end(); | |
378 | } | ||
379 | |||
380 | |||
381 |
2/2✓ Branch 0 taken 1000407 times.
✓ Branch 1 taken 21 times.
|
1000428 | Index StringAttributeWriteHandle::getIndex(const Name& name) const |
382 | { | ||
383 | // zero used for an empty string | ||
384 |
2/2✓ Branch 0 taken 1000407 times.
✓ Branch 1 taken 21 times.
|
1000428 | if (name.empty()) return Index(0); |
385 | |||
386 | const auto& cacheMap = mCache.map(); | ||
387 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1000406 times.
|
1000407 | auto it = cacheMap.find(name); |
388 | |||
389 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1000406 times.
|
1000407 | if (it == cacheMap.end()) { |
390 |
2/6✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
5 | OPENVDB_THROW(LookupError, "String does not exist in Metadata, insert it and reset the cache - \"" << name << "\"."); |
391 | } | ||
392 | |||
393 | 1000406 | return it->second; | |
394 | } | ||
395 | |||
396 | |||
397 | //////////////////////////////////////// | ||
398 | |||
399 | |||
400 | } // namespace points | ||
401 | } // namespace OPENVDB_VERSION_NAME | ||
402 | } // namespace openvdb | ||
403 |