| 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 |