1 // Copyright Contributors to the OpenVDB Project 2 // SPDX-License-Identifier: MPL-2.0 3 4 /// @file points/AttributeSet.h 5 /// 6 /// @authors Dan Bailey, Mihai Alden 7 /// 8 /// @brief Set of Attribute Arrays which tracks metadata about each array. 9 10 #ifndef OPENVDB_POINTS_ATTRIBUTE_SET_HAS_BEEN_INCLUDED 11 #define OPENVDB_POINTS_ATTRIBUTE_SET_HAS_BEEN_INCLUDED 12 13 #include "AttributeArray.h" 14 #include <openvdb/version.h> 15 #include <openvdb/MetaMap.h> 16 17 #include <climits> 18 #include <limits> 19 #include <memory> 20 #include <vector> 21 22 23 class TestAttributeSet; 24 25 26 namespace openvdb { 27 OPENVDB_USE_VERSION_NAMESPACE 28 namespace OPENVDB_VERSION_NAME { 29 namespace points { 30 31 32 using GroupType = uint8_t; 33 34 35 //////////////////////////////////////// 36 37 38 /// Ordered collection of uniquely-named attribute arrays 39 class OPENVDB_API AttributeSet 40 { 41 public: 42 enum { INVALID_POS = std::numeric_limits<size_t>::max() }; 43 44 using Ptr = std::shared_ptr<AttributeSet>; 45 using ConstPtr = std::shared_ptr<const AttributeSet>; 46 using UniquePtr = std::unique_ptr<AttributeSet>; 47 48 class Descriptor; 49 50 using DescriptorPtr = std::shared_ptr<Descriptor>; 51 using DescriptorConstPtr = std::shared_ptr<const Descriptor>; 52 53 ////////// 54 55 struct Util 56 { 57 /// Attribute and type name pair. 58 struct NameAndType { 59 NameAndType(const std::string& n, const NamePair& t, const Index s = 1) nameUtil::NameAndType60 : name(n), type(t), stride(s) {} 61 Name name; 62 NamePair type; 63 Index stride; 64 }; 65 66 using NameAndTypeVec = std::vector<NameAndType>; 67 using NameToPosMap = std::map<std::string, size_t>; 68 using GroupIndex = std::pair<size_t, uint8_t>; 69 }; 70 71 ////////// 72 73 AttributeSet(); 74 75 /// Construct a new AttributeSet from the given AttributeSet. 76 /// @param attributeSet the old attribute set 77 /// @param arrayLength the desired length of the arrays in the new AttributeSet 78 /// @param lock an optional scoped registry lock to avoid contention 79 /// @note This constructor is typically used to resize an existing AttributeSet as 80 /// it transfers attribute metadata such as hidden and transient flags 81 AttributeSet(const AttributeSet& attributeSet, Index arrayLength, 82 const AttributeArray::ScopedRegistryLock* lock = nullptr); 83 84 /// Construct a new AttributeSet from the given Descriptor. 85 /// @param descriptor stored in the new AttributeSet and used in construction 86 /// @param arrayLength the desired length of the arrays in the new AttributeSet 87 /// @param lock an optional scoped registry lock to avoid contention 88 /// @note Descriptors do not store attribute metadata such as hidden and transient flags 89 /// which live on the AttributeArrays, so for constructing from an existing AttributeSet 90 /// use the AttributeSet(const AttributeSet&, Index) constructor instead 91 AttributeSet(const DescriptorPtr& descriptor, Index arrayLength = 1, 92 const AttributeArray::ScopedRegistryLock* lock = nullptr); 93 94 /// Shallow copy constructor, the descriptor and attribute arrays will be shared. 95 AttributeSet(const AttributeSet&); 96 97 /// Disallow copy assignment, since it wouldn't be obvious whether the copy is deep or shallow. 98 AttributeSet& operator=(const AttributeSet&) = delete; 99 100 //@{ 101 /// @brief Return a reference to this attribute set's descriptor, which might 102 /// be shared with other sets. descriptor()103 Descriptor& descriptor() { return *mDescr; } descriptor()104 const Descriptor& descriptor() const { return *mDescr; } 105 //@} 106 107 /// @brief Return a pointer to this attribute set's descriptor, which might be 108 /// shared with other sets descriptorPtr()109 DescriptorPtr descriptorPtr() const { return mDescr; } 110 111 /// Return the number of attributes in this set. size()112 size_t size() const { return mAttrs.size(); } 113 114 /// Return the number of bytes of memory used by this attribute set. 115 size_t memUsage() const; 116 117 /// @brief Return the position of the attribute array whose name is @a name, 118 /// or @c INVALID_POS if no match is found. 119 size_t find(const std::string& name) const; 120 121 /// @brief Replace the attribute array whose name is @a name. 122 /// @return The position of the updated attribute array or @c INVALID_POS 123 /// if the given name does not exist or if the replacement failed because 124 /// the new array type does not comply with the descriptor. 125 size_t replace(const std::string& name, const AttributeArray::Ptr&); 126 127 /// @brief Replace the attribute array stored at position @a pos in this container. 128 /// @return The position of the updated attribute array or @c INVALID_POS 129 /// if replacement failed because the new array type does not comply with 130 /// the descriptor. 131 size_t replace(size_t pos, const AttributeArray::Ptr&); 132 133 //@{ 134 /// @brief Return a pointer to the attribute array whose name is @a name or 135 /// a null pointer if no match is found. 136 const AttributeArray* getConst(const std::string& name) const; 137 const AttributeArray* get(const std::string& name) const; 138 AttributeArray* get(const std::string& name); 139 //@} 140 141 //@{ 142 /// @brief Return a pointer to the attribute array stored at position @a pos 143 /// in this set. 144 const AttributeArray* getConst(size_t pos) const; 145 const AttributeArray* get(size_t pos) const; 146 AttributeArray* get(size_t pos); 147 //@} 148 149 //@{ 150 /// @brief Return the group offset from the name or index of the group 151 /// A group attribute array is a single byte (8-bit), each bit of which 152 /// can denote a group. The group offset is the position of the bit that 153 /// denotes the requested group if all group attribute arrays in the set 154 /// (and only attribute arrays marked as group) were to be laid out linearly 155 /// according to their order in the set. 156 size_t groupOffset(const Name& groupName) const; 157 size_t groupOffset(const Util::GroupIndex& index) const; 158 //@} 159 160 /// Return the group index from the name of the group 161 Util::GroupIndex groupIndex(const Name& groupName) const; 162 /// Return the group index from the offset of the group 163 /// @note see offset description for groupOffset() 164 Util::GroupIndex groupIndex(const size_t offset) const; 165 166 /// Return the indices of the attribute arrays which are group attribute arrays 167 std::vector<size_t> groupAttributeIndices() const; 168 169 /// Return true if the attribute array stored at position @a pos is shared. 170 bool isShared(size_t pos) const; 171 /// @brief If the attribute array stored at position @a pos is shared, 172 /// replace the array with a deep copy of itself that is not 173 /// shared with anyone else. 174 void makeUnique(size_t pos); 175 176 /// Append attribute @a attribute (simple method) 177 AttributeArray::Ptr appendAttribute(const Name& name, 178 const NamePair& type, 179 const Index strideOrTotalSize = 1, 180 const bool constantStride = true, 181 const Metadata* defaultValue = nullptr); 182 183 /// Append attribute @a attribute (descriptor-sharing) 184 /// Requires current descriptor to match @a expected 185 /// On append, current descriptor is replaced with @a replacement 186 /// Provide a @a lock object to avoid contention from appending in parallel 187 AttributeArray::Ptr appendAttribute(const Descriptor& expected, DescriptorPtr& replacement, 188 const size_t pos, const Index strideOrTotalSize = 1, 189 const bool constantStride = true, 190 const Metadata* defaultValue = nullptr, 191 const AttributeArray::ScopedRegistryLock* lock = nullptr); 192 193 /// @brief Remove and return an attribute array by name 194 /// @param name the name of the attribute array to release 195 /// @details Detaches the attribute array from this attribute set and returns it, if 196 /// @a name is invalid, returns an empty shared pointer. This also updates the descriptor 197 /// to remove the reference to the attribute array. 198 /// @note AttributeArrays are stored as shared pointers, so they are not guaranteed 199 /// to be unique. Check the reference count before blindly re-using in a new AttributeSet. 200 AttributeArray::Ptr removeAttribute(const Name& name); 201 202 /// @brief Remove and return an attribute array by index 203 /// @param pos the position index of the attribute to release 204 /// @details Detaches the attribute array from this attribute set and returns it, if 205 /// @a pos is invalid, returns an empty shared pointer. This also updates the descriptor 206 /// to remove the reference to the attribute array. 207 /// @note AttributeArrays are stored as shared pointers, so they are not guaranteed 208 /// to be unique. Check the reference count before blindly re-using in a new AttributeSet. 209 AttributeArray::Ptr removeAttribute(const size_t pos); 210 211 /// @brief Remove and return an attribute array by index (unsafe method) 212 /// @param pos the position index of the attribute to release 213 /// @details Detaches the attribute array from this attribute set and returns it, if 214 /// @a pos is invalid, returns an empty shared pointer. 215 /// In cases where the AttributeSet is due to be destroyed, a small performance 216 /// advantage can be gained by leaving the attribute array as a nullptr and not 217 /// updating the descriptor. However, this leaves the AttributeSet in an invalid 218 /// state making it unsafe to call any methods that implicitly derefence the attribute array. 219 /// @note AttributeArrays are stored as shared pointers, so they are not guaranteed 220 /// to be unique. Check the reference count before blindly re-using in a new AttributeSet. 221 /// @warning Only use this method if you're an expert and know the risks of not 222 /// updating the array of attributes or the descriptor. 223 AttributeArray::Ptr removeAttributeUnsafe(const size_t pos); 224 225 /// Drop attributes with @a pos indices (simple method) 226 /// Creates a new descriptor for this attribute set 227 void dropAttributes(const std::vector<size_t>& pos); 228 229 /// Drop attributes with @a pos indices (descriptor-sharing method) 230 /// Requires current descriptor to match @a expected 231 /// On drop, current descriptor is replaced with @a replacement 232 void dropAttributes(const std::vector<size_t>& pos, 233 const Descriptor& expected, DescriptorPtr& replacement); 234 235 /// Re-name attributes in set to match a provided descriptor 236 /// Replaces own descriptor with @a replacement 237 void renameAttributes(const Descriptor& expected, const DescriptorPtr& replacement); 238 239 /// Re order attribute set to match a provided descriptor 240 /// Replaces own descriptor with @a replacement 241 void reorderAttributes(const DescriptorPtr& replacement); 242 243 /// Replace the current descriptor with a @a replacement 244 /// Note the provided Descriptor must be identical to the replacement 245 /// unless @a allowMismatchingDescriptors is true (default is false) 246 void resetDescriptor(const DescriptorPtr& replacement, const bool allowMismatchingDescriptors = false); 247 248 /// Read the entire set from a stream. 249 void read(std::istream&); 250 /// Write the entire set to a stream. 251 /// @param outputTransient if true, write out transient attributes 252 void write(std::ostream&, bool outputTransient = false) const; 253 254 /// This will read the attribute descriptor from a stream. 255 void readDescriptor(std::istream&); 256 /// This will write the attribute descriptor to a stream. 257 /// @param outputTransient if true, write out transient attributes 258 void writeDescriptor(std::ostream&, bool outputTransient = false) const; 259 260 /// This will read the attribute metadata from a stream. 261 void readMetadata(std::istream&); 262 /// This will write the attribute metadata to a stream. 263 /// @param outputTransient if true, write out transient attributes 264 /// @param paged if true, data is written out in pages 265 void writeMetadata(std::ostream&, bool outputTransient = false, bool paged = false) const; 266 267 /// This will read the attribute data from a stream. 268 void readAttributes(std::istream&); 269 /// This will write the attribute data to a stream. 270 /// @param outputTransient if true, write out transient attributes 271 void writeAttributes(std::ostream&, bool outputTransient = false) const; 272 273 /// Compare the descriptors and attribute arrays on the attribute sets 274 /// Exit early if the descriptors do not match 275 bool operator==(const AttributeSet& other) const; 276 bool operator!=(const AttributeSet& other) const { return !this->operator==(other); } 277 278 private: 279 using AttrArrayVec = std::vector<AttributeArray::Ptr>; 280 281 DescriptorPtr mDescr; 282 AttrArrayVec mAttrs; 283 }; // class AttributeSet 284 285 //////////////////////////////////////// 286 287 288 /// A container for ABI=5 to help ease introduction of upcoming features 289 namespace future { 290 class Container 291 { 292 class Element { }; 293 std::vector<std::shared_ptr<Element>> mElements; 294 }; 295 } 296 297 298 //////////////////////////////////////// 299 300 301 /// @brief An immutable object that stores name, type and AttributeSet position 302 /// for a constant collection of attribute arrays. 303 /// @note The attribute name is actually mutable, but the attribute type 304 /// and position can not be changed after creation. 305 class OPENVDB_API AttributeSet::Descriptor 306 { 307 public: 308 using Ptr = std::shared_ptr<Descriptor>; 309 310 using NameAndType = Util::NameAndType; 311 using NameAndTypeVec = Util::NameAndTypeVec; 312 using GroupIndex = Util::GroupIndex; 313 using NameToPosMap = Util::NameToPosMap; 314 using ConstIterator = NameToPosMap::const_iterator; 315 316 /// Utility method to construct a NameAndType sequence. 317 struct Inserter { 318 NameAndTypeVec vec; addInserter319 Inserter& add(const NameAndType& nameAndType) { 320 vec.push_back(nameAndType); return *this; 321 } addInserter322 Inserter& add(const Name& name, const NamePair& type) { 323 vec.emplace_back(name, type); return *this; 324 } addInserter325 Inserter& add(const NameAndTypeVec& other) { 326 for (NameAndTypeVec::const_iterator it = other.begin(), itEnd = other.end(); it != itEnd; ++it) { 327 vec.emplace_back(it->name, it->type); 328 } 329 return *this; 330 } 331 }; 332 333 ////////// 334 335 Descriptor(); 336 337 /// Copy constructor 338 Descriptor(const Descriptor&); 339 340 /// Create a new descriptor from a position attribute type and assumes "P" (for convenience). 341 static Ptr create(const NamePair&); 342 343 /// Create a new descriptor as a duplicate with a new attribute appended 344 Ptr duplicateAppend(const Name& name, const NamePair& type) const; 345 346 /// Create a new descriptor as a duplicate with existing attributes dropped 347 Ptr duplicateDrop(const std::vector<size_t>& pos) const; 348 349 /// Return the number of attributes in this descriptor. size()350 size_t size() const { return mTypes.size(); } 351 352 /// Return the number of attributes with this attribute type 353 size_t count(const NamePair& type) const; 354 355 /// Return the number of bytes of memory used by this attribute set. 356 size_t memUsage() const; 357 358 /// @brief Return the position of the attribute array whose name is @a name, 359 /// or @c INVALID_POS if no match is found. 360 size_t find(const std::string& name) const; 361 362 /// Rename an attribute array 363 size_t rename(const std::string& fromName, const std::string& toName); 364 365 /// Return the name of the attribute array's type. 366 const Name& valueType(size_t pos) const; 367 /// Return the name of the attribute array's type. 368 const NamePair& type(size_t pos) const; 369 370 /// Retrieve metadata map 371 MetaMap& getMetadata(); 372 const MetaMap& getMetadata() const; 373 374 /// Return true if the attribute has a default value 375 bool hasDefaultValue(const Name& name) const; 376 /// Get a default value for an existing attribute 377 template<typename ValueType> getDefaultValue(const Name & name)378 ValueType getDefaultValue(const Name& name) const 379 { 380 const size_t pos = find(name); 381 if (pos == INVALID_POS) { 382 OPENVDB_THROW(LookupError, "Cannot find attribute name to set default value.") 383 } 384 385 std::stringstream ss; 386 ss << "default:" << name; 387 388 auto metadata = mMetadata.getMetadata<TypedMetadata<ValueType>>(ss.str()); 389 390 if (metadata) return metadata->value(); 391 392 return zeroVal<ValueType>(); 393 } 394 /// Set a default value for an existing attribute 395 void setDefaultValue(const Name& name, const Metadata& defaultValue); 396 // Remove the default value if it exists 397 void removeDefaultValue(const Name& name); 398 // Prune any default values for which the key is no longer present 399 void pruneUnusedDefaultValues(); 400 401 /// Return true if this descriptor is equal to the given one. 402 bool operator==(const Descriptor&) const; 403 /// Return true if this descriptor is not equal to the given one. 404 bool operator!=(const Descriptor& rhs) const { return !this->operator==(rhs); } 405 /// Return true if this descriptor contains the same attributes 406 /// as the given descriptor, ignoring attribute order 407 bool hasSameAttributes(const Descriptor& rhs) const; 408 409 /// Return a reference to the name-to-position map. map()410 const NameToPosMap& map() const { return mNameMap; } 411 /// Return a reference to the name-to-position group map. groupMap()412 const NameToPosMap& groupMap() const { return mGroupMap; } 413 414 /// Return @c true if group exists 415 bool hasGroup(const Name& group) const; 416 /// @brief Define a group name to offset mapping 417 /// @param group group name 418 /// @param offset group offset 419 /// @param checkValidOffset throws if offset out-of-range or in-use 420 void setGroup(const Name& group, const size_t offset, 421 const bool checkValidOffset = false); 422 /// Drop any mapping keyed by group name 423 void dropGroup(const Name& group); 424 /// Clear all groups 425 void clearGroups(); 426 /// Rename a group 427 size_t renameGroup(const std::string& fromName, const std::string& toName); 428 /// Return a unique name for a group based on given name 429 const Name uniqueGroupName(const Name& name) const; 430 431 //@{ 432 /// @brief Return the group offset from the name or index of the group 433 /// A group attribute array is a single byte (8-bit), each bit of which 434 /// can denote a group. The group offset is the position of the bit that 435 /// denotes the requested group if all group attribute arrays in the set 436 /// (and only attribute arrays marked as group) were to be laid out linearly 437 /// according to their order in the set. 438 size_t groupOffset(const Name& groupName) const; 439 size_t groupOffset(const GroupIndex& index) const; 440 //@} 441 442 /// Return the group index from the name of the group 443 GroupIndex groupIndex(const Name& groupName) const; 444 /// Return the group index from the offset of the group 445 /// @note see offset description for groupOffset() 446 GroupIndex groupIndex(const size_t offset) const; 447 448 /// Return number of bits occupied by a group attribute array groupBits()449 static size_t groupBits() { return sizeof(GroupType) * CHAR_BIT; } 450 451 /// Return the total number of available groups 452 /// (group bits * number of group attributes) 453 size_t availableGroups() const; 454 455 /// Return the number of empty group slots which correlates to the number of groups 456 /// that can be stored without increasing the number of group attribute arrays 457 size_t unusedGroups() const; 458 459 /// Return @c true if there are sufficient empty slots to allow compacting 460 bool canCompactGroups() const; 461 462 /// @brief Return a group offset that is not in use 463 /// @param hint if provided, request a specific offset as a hint 464 /// @return index of an offset or size_t max if no available group offsets 465 size_t unusedGroupOffset(size_t hint = std::numeric_limits<size_t>::max()) const; 466 467 /// @brief Determine if a move is required to efficiently compact the data and store the 468 /// source name, offset and the target offset in the input parameters 469 /// @param sourceName source name 470 /// @param sourceOffset source offset 471 /// @param targetOffset target offset 472 /// @return @c true if move is required to compact the data 473 bool requiresGroupMove(Name& sourceName, size_t& sourceOffset, size_t& targetOffset) const; 474 475 /// @brief Test if there are any group names shared by both descriptors which 476 /// have a different index 477 /// @param rhs the descriptor to compare with 478 /// @return @c true if an index collision exists 479 bool groupIndexCollision(const Descriptor& rhs) const; 480 481 /// Return a unique name for an attribute array based on given name 482 const Name uniqueName(const Name& name) const; 483 484 /// Return true if the name is valid 485 static bool validName(const Name& name); 486 487 /// @brief Extract each name from @a nameStr into @a includeNames, or into @a excludeNames 488 /// if the name is prefixed with a caret. 489 /// @param nameStr the input string of names 490 /// @param includeNames on exit, the list of names that are not prefixed with a caret 491 /// @param excludeNames on exit, the list of names that are prefixed with a caret 492 /// @param includeAll on exit, @c true if a "*" wildcard is present in the @a includeNames 493 static void parseNames( std::vector<std::string>& includeNames, 494 std::vector<std::string>& excludeNames, 495 bool& includeAll, 496 const std::string& nameStr); 497 498 /// @brief Extract each name from @a nameStr into @a includeNames, or into @a excludeNames 499 /// if the name is prefixed with a caret. 500 static void parseNames( std::vector<std::string>& includeNames, 501 std::vector<std::string>& excludeNames, 502 const std::string& nameStr); 503 504 /// Serialize this descriptor to the given stream. 505 void write(std::ostream&) const; 506 /// Unserialize this transform from the given stream. 507 void read(std::istream&); 508 509 protected: 510 /// Append to a vector of names and types from this Descriptor in position order 511 void appendTo(NameAndTypeVec& attrs) const; 512 513 /// Create a new descriptor from the given attribute and type name pairs 514 /// and copy the group maps and metamap. 515 static Ptr create(const NameAndTypeVec&, const NameToPosMap&, const MetaMap&); 516 517 size_t insert(const std::string& name, const NamePair& typeName); 518 519 private: 520 friend class ::TestAttributeSet; 521 522 NameToPosMap mNameMap; 523 std::vector<NamePair> mTypes; 524 NameToPosMap mGroupMap; 525 MetaMap mMetadata; 526 // as this change is part of an ABI change, there's no good reason to reduce the reserved 527 // space aside from keeping the memory size of an AttributeSet the same for convenience 528 // (note that this assumes a typical three-pointer implementation for std::vector) 529 future::Container mFutureContainer; // occupies 3 reserved slots 530 int64_t mReserved[5]; // for future use 531 }; // class Descriptor 532 533 } // namespace points 534 } // namespace OPENVDB_VERSION_NAME 535 } // namespace openvdb 536 537 #endif // OPENVDB_POINTS_ATTRIBUTE_ARRAY_HAS_BEEN_INCLUDED 538