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