diff --git a/src/Magnum/Trade/Implementation/sceneTools.h b/src/Magnum/Trade/Implementation/sceneTools.h index 6336dc43e..a4242f6c3 100644 --- a/src/Magnum/Trade/Implementation/sceneTools.h +++ b/src/Magnum/Trade/Implementation/sceneTools.h @@ -109,7 +109,7 @@ inline SceneData sceneCombine(const SceneMappingType mappingType, const Unsigned for(std::size_t i = 0; i != fields.size(); ++i) { const SceneFieldData& field = fields[i]; - CORRADE_INTERNAL_ASSERT(!field.isOffsetOnly()); + CORRADE_INTERNAL_ASSERT(!(field.flags() & SceneFieldFlag::OffsetOnly)); /* Mapping data. Allocate if the view is a placeholder of if it wasn't used by other fields yet. */ diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 44a19a77c..a2ad3a325 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -25,9 +25,12 @@ #include "SceneData.h" +#include +#include #include #include #include +#include #include #include @@ -473,7 +476,32 @@ UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { CORRADE_ASSERT_UNREACHABLE("Trade::sceneFieldTypeAlignment(): invalid type" << type, {}); } -SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, const UnsignedShort fieldArraySize) noexcept: SceneFieldData{name, {}, Containers::StridedArrayView1D{{mappingData.data(), ~std::size_t{}}, mappingData.size()[0], mappingData.stride()[0]}, fieldType, Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, fieldArraySize} { +Debug& operator<<(Debug& debug, const SceneFieldFlag value) { + debug << "Trade::SceneFieldFlag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneFieldFlag::value: return debug << "::" #value; + _c(OffsetOnly) + _c(ImplicitMapping) + _c(OrderedMapping) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const SceneFieldFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::SceneFieldFlags{}", { + SceneFieldFlag::OffsetOnly, + SceneFieldFlag::ImplicitMapping, + /* This one is implied by ImplicitMapping, so has to be after */ + SceneFieldFlag::OrderedMapping + }); +} + +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: SceneFieldData{name, {}, Containers::StridedArrayView1D{{mappingData.data(), ~std::size_t{}}, mappingData.size()[0], mappingData.stride()[0]}, fieldType, Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, fieldArraySize, flags} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ @@ -565,7 +593,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp if(field._size) { const UnsignedInt fieldTypeSize = sceneFieldTypeSize(field._fieldType)* (field._fieldArraySize ? field._fieldArraySize : 1); - if(field._isOffsetOnly) { + if(field._flags & SceneFieldFlag::OffsetOnly) { const std::size_t mappingSize = field._mappingData.offset + (field._size - 1)*field._mappingStride + mappingTypeSize; const std::size_t fieldSize = field._fieldData.offset + (field._size - 1)*field._fieldStride + fieldTypeSize; CORRADE_ASSERT(mappingSize <= _data.size(), @@ -780,7 +808,7 @@ Containers::StridedArrayView1D SceneData::fieldDataMappingViewIntern CORRADE_INTERNAL_ASSERT(offset + size <= field._size); return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ - {static_cast(field._isOffsetOnly ? + {static_cast(field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._mappingData.offset : field._mappingData.pointer) + field._mappingStride*offset, ~std::size_t{}}, size, field._mappingStride @@ -795,7 +823,7 @@ Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal CORRADE_INTERNAL_ASSERT(offset + size <= field._size); return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ - {static_cast(field._isOffsetOnly ? + {static_cast(field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : field._fieldData.pointer) + field._fieldStride*offset, ~std::size_t{}}, size, field._fieldStride}; @@ -809,7 +837,7 @@ SceneFieldData SceneData::fieldData(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldData(): index" << id << "out of range for" << _fields.size() << "fields", SceneFieldData{}); const SceneFieldData& field = _fields[id]; - return SceneFieldData{field._name, field._mappingType, fieldDataMappingViewInternal(field), field._fieldType, fieldDataFieldViewInternal(field), field._fieldArraySize}; + return SceneFieldData{field._name, field._mappingType, fieldDataMappingViewInternal(field), field._fieldType, fieldDataFieldViewInternal(field), field._fieldArraySize, field._flags & ~SceneFieldFlag::OffsetOnly}; } SceneField SceneData::fieldName(const UnsignedInt id) const { @@ -818,6 +846,12 @@ SceneField SceneData::fieldName(const UnsignedInt id) const { return _fields[id]._name; } +SceneFieldFlags SceneData::fieldFlags(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldFlags(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._flags; +} + SceneFieldType SceneData::fieldType(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldType(): index" << id << "out of range for" << _fields.size() << "fields", {}); @@ -859,11 +893,32 @@ bool SceneData::hasField(const SceneField name) const { namespace { -template std::size_t findObject(const Containers::StridedArrayView1D& mapping, const UnsignedInt object) { +/* The `objects` view is already adjusted for `offset`, the offset is needed + only to return the correct value for ImplicitMapping */ +template std::size_t findObject(const SceneFieldFlags flags, const Containers::StridedArrayView1D& mapping, const std::size_t offset, const UnsignedInt object) { + const std::size_t max = mapping.size(); + + /* Implicit mapping, position equals object ID (if in bounds) and thus an + O(1)-complexity search. A superset of OrderedMapping so has to be + before. */ + if(flags >= SceneFieldFlag::ImplicitMapping) + return object >= offset && object - offset < mapping.size() ? + object - offset : max; + const Containers::StridedArrayView1D mappingT = Containers::arrayCast(mapping); - const std::size_t max = mappingT.size(); - /** @todo implement something faster than O(n) when field-specific flags - can annotate how the object mapping is done */ + + /* Ordered mapping, so an O(log n)-complexity search. It also has to be + noted that STL algorithms generally suck (this needs a + std::iterator_traits specialization for the StridedArrayView, FFS!), + std::binary_search() is useless because it returns just a bool (!!) and + std::lower_bound() is error prone beyond any reason. */ + if(flags >= SceneFieldFlag::OrderedMapping) { + const Containers::StridedIterator<1, const T> found = std::lower_bound(mappingT.begin(), mappingT.end(), T(object)); + if(found == mappingT.end() || *found != object) return max; + return found - mappingT.begin(); + } + + /* Generally unordered container, O(n)-complexity search. */ for(std::size_t i = 0; i != max; ++i) if(mappingT[i] == object) return i; return max; @@ -874,13 +929,13 @@ template std::size_t findObject(const Containers::StridedArrayView1D mapping = fieldDataMappingViewInternal(field, offset, field._size - offset); if(field._mappingType == SceneMappingType::UnsignedInt) - return offset + findObject(mapping, object); + return offset + findObject(field._flags, mapping, offset, object); else if(field._mappingType == SceneMappingType::UnsignedShort) - return offset + findObject(mapping, object); + return offset + findObject(field._flags, mapping, offset, object); else if(field._mappingType == SceneMappingType::UnsignedByte) - return offset + findObject(mapping, object); + return offset + findObject(field._flags, mapping, offset, object); else if(field._mappingType == SceneMappingType::UnsignedLong) - return offset + findObject(mapping, object); + return offset + findObject(field._flags, mapping, offset, object); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -970,6 +1025,12 @@ bool SceneData::hasFieldObject(const SceneField fieldName, const UnsignedInt obj return findFieldObjectOffsetInternal(field, object, 0) != field._size; } +SceneFieldFlags SceneData::fieldFlags(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldFlags(): field" << name << "not found", {}); + return _fields[fieldId]._flags; +} + SceneFieldType SceneData::fieldType(const SceneField name) const { const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldType(): field" << name << "not found", {}); diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 5959182e1..53feab568 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Trade::SceneData, @ref Magnum::Trade::SceneFieldData, enum @ref Magnum::Trade::SceneMappingType, @ref Magnum::Trade::SceneField, @ref Magnum::Trade::SceneFieldType, function @ref Magnum::sceneMappingTypeSize(), @ref Magnum::sceneMappingTypeAlignment(), @ref Magnum::sceneFieldTypeSize(), @ref Magnum::sceneFieldTypeAlignment(), @ref Magnum::Trade::isSceneFieldCustom(), @ref Magnum::sceneFieldCustom() + * @brief Class @ref Magnum::Trade::SceneData, @ref Magnum::Trade::SceneFieldData, enum @ref Magnum::Trade::SceneMappingType, @ref Magnum::Trade::SceneField, @ref Magnum::Trade::SceneFieldType, @ref Magnum::Trade::SceneFieldFlag, enum set @ref Magnum::Trade::SceneFieldFlags, function @ref Magnum::sceneMappingTypeSize(), @ref Magnum::sceneMappingTypeAlignment(), @ref Magnum::sceneFieldTypeSize(), @ref Magnum::sceneFieldTypeAlignment(), @ref Magnum::Trade::isSceneFieldCustom(), @ref Magnum::sceneFieldCustom() */ #include @@ -527,6 +527,80 @@ MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeSize(SceneFieldType type); */ MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeAlignment(SceneFieldType type); +/** +@brief Scene field flag +@m_since_latest + +@see @ref SceneFieldFlags, @ref SceneFieldData, @ref SceneFieldData::flags(), + @ref SceneData::fieldFlags() +*/ +enum class SceneFieldFlag: UnsignedByte { + /** + * The field is offset-only, i.e. doesn't contain the data views directly + * but referes to unspecified external data. Set implicitly by + * the @ref SceneFieldData::SceneFieldData(SceneField, std::size_t, SceneMappingType, std::size_t, std::ptrdiff_t, SceneFieldType, std::size_t, std::ptrdiff_t, UnsignedShort, SceneFieldFlags) + * constructor, can't be used for any other constructor. + * @see @ref SceneFieldData::mappingData(Containers::ArrayView) const, + * @ref SceneFieldData::fieldData(Containers::ArrayView) const + */ + OffsetOnly = 1 << 0, + + /** + * The field has an ordered object mapping, i.e. a monotonically increasing + * sequence. Object IDs in fields marked with this flag can be looked up + * with an @f$ \mathcal{O}(\log{} n) @f$ complexity, gaps and duplicates + * are possible. + * + * Note that validity of the object mapping data isn't checked in any way + * and if the data doesn't correspond to rules of the flag, queries such + * as @ref SceneData::findFieldObjectOffset() may return a wrong value. + * + * If a field has neither this nor the @ref SceneFieldFlag::ImplicitMapping + * flag, it's assumed to be unordered, with an + * @f$ \mathcal{O}(n) @f$ lookup complexity. + */ + OrderedMapping = 1 << 1, + + /** + * The field has an implicit object mapping, i.e. a contiguous sequence + * from 0 up to size of the field. A superset of + * @ref SceneFieldFlag::OrderedMapping. Object IDs in fields marked with + * this flag can be looked up with an @f$ \mathcal{O}(1) @f$ complexity, + * but the field is restricted to exactly one value for each object. + * + * Note that validity of the object mapping data isn't checked in any way + * and if the data doesn't correspond to rules of the flag, queries such + * as @ref SceneData::findFieldObjectOffset() may return a wrong value. + * + * If a field has neither this nor the @ref SceneFieldFlag::OrderedMapping + * flag, it's assumed to be unordered, with an + * @f$ \mathcal{O}(n) @f$ lookup complexity. + */ + ImplicitMapping = (1 << 2)|OrderedMapping, +}; + +/** +@debugoperatorenum{SceneFieldFlag} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldFlag value); + +/** +@brief Scene field flags +@m_since_latest + +@see @ref SceneFieldData::flags(), @ref SceneData::fieldFlags() +*/ +typedef Containers::EnumSet SceneFieldFlags; + +CORRADE_ENUMSET_OPERATORS(SceneFieldFlags) + +/** +@debugoperatorenum{SceneFieldFlags} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldFlags value); + /** @brief Scene field data @m_since_latest @@ -555,6 +629,25 @@ the data layout is static (and thus can be defined at compile time), but the actual data is allocated / populated at runtime: @snippet MagnumTrade.cpp SceneFieldData-usage-offset-only + +Offset-only fields are marked with @ref SceneFieldFlag::OffsetOnly in +@ref flags(). + +@subsection Trade-SceneFieldData-usage-object-mapping Ordered and implicit object mapping + +If you can guarantee the object mapping field is monotonically non-decreasing, +it's recommended to annotate it with @ref SceneFieldFlag::OrderedMapping. This +makes certain convenience APIs such as @ref SceneData::findFieldObjectOffset() +or e.g. @relativeref{SceneData,transformation3DFor()} perform the lookup in +@f$ \mathcal{O}(\log{} n) @f$ instead of @f$ \mathcal{O}(n) @f$. Data consuming +algorithms on the application side can then also adapt based on what flags are +present in @ref SceneData::fieldFlags(). + +In some cases the object mapping is even implicit, i.e. the first entry of the +field specifying data for object @cpp 0 @ce, second entry for object +@cpp 1 @ce, third for object @cpp 2 @ce and so on. You can annotate such fields +with @ref SceneFieldFlag::ImplicitMapping, which is a superset of +@relativeref{SceneFieldFlag,OrderedMapping}. */ class MAGNUM_TRADE_EXPORT SceneFieldData { public: @@ -565,7 +658,7 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * initialization of the field array for @ref SceneData, expected to be * replaced with concrete values later. */ - constexpr explicit SceneFieldData() noexcept: _size{}, _name{}, _isOffsetOnly{}, _mappingType{}, _mappingStride{}, _mappingData{}, _fieldType{}, _fieldStride{}, _fieldArraySize{}, _fieldData{} {} + constexpr explicit SceneFieldData() noexcept: _size{}, _name{}, _flags{}, _mappingType{}, _mappingStride{}, _mappingData{}, _fieldType{}, _fieldStride{}, _fieldArraySize{}, _fieldData{} {} /** * @brief Type-erased constructor @@ -576,12 +669,17 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param fieldData Field data * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is not allowed here. * * Expects that @p mappingData and @p fieldData have the same size, * @p fieldType corresponds to @p name and @p fieldArraySize is zero * for builtin fields. */ - constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, UnsignedShort fieldArraySize = 0) noexcept; + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingType, mappingData, fieldType, fieldData, 0, flags} {} /** * @brief Constructor @@ -591,6 +689,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param fieldData Field data * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is not allowed here. * * Expects that @p mappingData and @p fieldData have the same size in * the first dimension, that the second dimension of @p mappingData is @@ -600,51 +700,58 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @p fieldArraySize and that @p fieldType corresponds to @p name and * @p fieldArraySize is zero for builtin attributes. */ - explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, UnsignedShort fieldArraySize = 0) noexcept; + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingData, fieldType, fieldData, 0, flags} {} /** * @brief Constructor * @param name Field name * @param mappingData Object mapping data * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. * * Detects @ref SceneMappingType based on @p T and @ref SceneFieldType - * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort). + * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags). * For all types known by Magnum, the detected @ref SceneFieldType is * of the same name as the type (so e.g. @relativeref{Magnum,Vector3ui} * gets recognized as @ref SceneFieldType::Vector3ui). */ - template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData) noexcept; + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; /** @overload */ - template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::ArrayView& fieldData) noexcept: SceneFieldData{name, mappingData, Containers::stridedArrayView(fieldData)} {} + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::ArrayView& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, mappingData, Containers::stridedArrayView(fieldData), flags} {} /** @overload */ - template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedArrayView1D& fieldData) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), fieldData} {} + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), fieldData, flags} {} /** @overload */ - template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::ArrayView& fieldData) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), Containers::stridedArrayView(fieldData)} {} + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::ArrayView& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), Containers::stridedArrayView(fieldData), flags} {} /** * @brief Construct an array field * @param name Field name * @param mappingData Object mapping data * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. * * Detects @ref SceneMappingType based on @p T and @ref SceneFieldType - * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort) + * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags) * with the @p fieldData second dimension size passed to * @p fieldArraySize. Expects that the second dimension of @p fieldData * is contiguous. At the moment only custom fields can be arrays, which * means this function can't be used with a builtin @p name. See - * @ref SceneFieldData(SceneField, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * @ref SceneFieldData(SceneField, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, SceneFieldFlags) * for details about @ref SceneMappingType and @ref SceneFieldType * detection. */ - template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView2D& fieldData) noexcept; + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept; /** @overload */ - template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedArrayView2D& fieldData) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), fieldData} {} + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), fieldData, flags} {} /** * @brief Construct an offset-only field @@ -658,6 +765,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param fieldStride Field data stride * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is set implicitly. * * Instances created this way refer to offsets in unspecified * external scene data instead of containing the data views directly. @@ -668,24 +777,19 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * Note that due to the @cpp constexpr @ce nature of this constructor, * no @p mappingType checks against @p mappingStride or * @p fieldType / @p fieldArraySize checks against @p fieldStride can - * be done. You're encouraged to use the @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort) + * be done. You're encouraged to use the @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags) * constructor if you want additional safeguards. - * @see @ref isOffsetOnly(), @ref fieldArraySize(), + * @see @ref flags(), @ref fieldArraySize(), * @ref mappingData(Containers::ArrayView) const, * @ref fieldData(Containers::ArrayView) const */ - explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneMappingType mappingType, std::size_t mappingOffset, std::ptrdiff_t mappingStride, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize = 0) noexcept; + explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneMappingType mappingType, std::size_t mappingOffset, std::ptrdiff_t mappingStride, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; - /** - * @brief If the field is offset-only - * - * Returns @cpp true @ce if the field doesn't contain the data views - * directly, but instead refers to unspecified external data. - * @see @ref mappingData(Containers::ArrayView) const, - * @ref fieldData(Containers::ArrayView) const, - * @ref SceneFieldData(SceneField, std::size_t, SceneMappingType, std::size_t, std::ptrdiff_t, SceneFieldType, std::size_t, std::ptrdiff_t, UnsignedShort) - */ - constexpr bool isOffsetOnly() const { return _isOffsetOnly; } + /** @overload */ + explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneMappingType mappingType, std::size_t mappingOffset, std::ptrdiff_t mappingStride, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, SceneFieldFlags flags) noexcept: SceneFieldData{name, size, mappingType, mappingOffset, mappingStride, fieldType, fieldOffset, fieldStride, 0, flags} {} + + /** @brief Field flags */ + constexpr SceneFieldFlags flags() const { return _flags; } /** @brief Field name */ constexpr SceneField name() const { return _name; } @@ -699,30 +803,31 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { /** * @brief Type-erased object mapping data * - * Expects that the field is not offset-only, in that case use the - * @ref mappingData(Containers::ArrayView) const overload - * instead. - * @see @ref isOffsetOnly() + * Expects that the field does not have @ref SceneFieldFlag::OffsetOnly + * set, in that case use the @ref mappingData(Containers::ArrayView) const + * overload instead. + * @see @ref flags() */ constexpr Containers::StridedArrayView1D mappingData() const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ {_mappingData.pointer, ~std::size_t{}}, _size, - (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::SceneFieldData::mappingData(): the field is offset-only, supply a data array"), _mappingStride)}; + (CORRADE_CONSTEXPR_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), "Trade::SceneFieldData::mappingData(): the field is offset-only, supply a data array"), _mappingStride)}; } /** * @brief Type-erased object mapping data for an offset-only attribute * - * If the field is not offset-only, the @p data parameter is ignored. - * @see @ref isOffsetOnly(), @ref mappingData() const + * If the field does not have @ref SceneFieldFlag::OffsetOnly set, the + * @p data parameter is ignored. + * @see @ref flags(), @ref mappingData() const */ Containers::StridedArrayView1D mappingData(Containers::ArrayView data) const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - data, _isOffsetOnly ? reinterpret_cast(data.data()) + _mappingData.offset : _mappingData.pointer, _size, _mappingStride}; + data, _flags & SceneFieldFlag::OffsetOnly ? reinterpret_cast(data.data()) + _mappingData.offset : _mappingData.pointer, _size, _mappingStride}; } /** @brief Field type */ @@ -734,30 +839,31 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { /** * @brief Type-erased field data * - * Expects that the field is not offset-only, in that case use the - * @ref fieldData(Containers::ArrayView) const overload - * instead. - * @see @ref isOffsetOnly() + * Expects that the field does not have @ref SceneFieldFlag::OffsetOnly + * set, in that case use the @ref fieldData(Containers::ArrayView) const + * overload instead. + * @see @ref flags() */ constexpr Containers::StridedArrayView1D fieldData() const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ {_fieldData.pointer, ~std::size_t{}}, _size, - (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array"), _fieldStride)}; + (CORRADE_CONSTEXPR_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array"), _fieldStride)}; } /** * @brief Type-erased field data for an offset-only attribute * - * If the field is not offset-only, the @p data parameter is ignored. - * @see @ref isOffsetOnly(), @ref fieldData() const + * If the field does not have @ref SceneFieldFlag::OffsetOnly set, the + * @p data parameter is ignored. + * @see @ref flags(), @ref fieldData() const */ Containers::StridedArrayView1D fieldData(Containers::ArrayView data) const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - data, _isOffsetOnly ? reinterpret_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _fieldStride}; + data, _flags & SceneFieldFlag::OffsetOnly ? reinterpret_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _fieldStride}; } private: @@ -776,7 +882,7 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { UnsignedLong _size; SceneField _name; - bool _isOffsetOnly; + SceneFieldFlags _flags; SceneMappingType _mappingType; Short _mappingStride; Data _mappingData; @@ -985,10 +1091,12 @@ with a "Chair" object, assuming such object exists: @snippet MagnumTrade.cpp SceneData-per-object -Since these APIs perform a linear lookup through the field and object arrays, -these are suited mainly for introspection and debugging purposes. Retrieving -field data for many objects is better achieved by accessing the field data -directly. +The actual object ID lookup is done by @ref findFieldObjectOffset() and +depending on what @ref SceneFieldFlags are present for given field, it can be +done in constant, logarithmic or, worst case, linear time. As such, for general +scene representations these are suited mainly for introspection and debugging +purposes and retrieving field data for many objects is better achieved by +accessing the field data directly. @section Trade-SceneData-usage-mutable Mutable data access @@ -1219,9 +1327,10 @@ class MAGNUM_TRADE_EXPORT SceneData { * @ref fieldSize() and @ref fieldArraySize() accessors. Compared to * those and to @ref fieldData(UnsignedInt) const, the * @ref SceneFieldData instances returned by this function may have - * different data pointers, and some of them might be offset-only --- - * use this function only if you *really* know what are you doing. - * @see @ref SceneFieldData::isOffsetOnly() + * different data pointers, and some of them might have + * @ref SceneFieldFlag::OffsetOnly set --- use this function only if + * you *really* know what are you doing. + * @see @ref SceneFieldData::flags() */ Containers::ArrayView fieldData() const & { return _fields; } @@ -1244,9 +1353,9 @@ class MAGNUM_TRADE_EXPORT SceneData { * * Unlike with @ref fieldData() and @ref releaseFieldData(), returned * instances are guaranteed to always have an absolute data pointer - * (i.e., @ref SceneFieldData::isOffsetOnly() always returning - * @cpp false @ce). The @p id is expected to be smaller than - * @ref fieldCount(). + * (i.e., @ref SceneFieldData::flags() never having + * @ref SceneFieldFlag::OffsetOnly set). The @p id is expected to be + * smaller than @ref fieldCount(). */ SceneFieldData fieldData(UnsignedInt id) const; @@ -1261,6 +1370,15 @@ class MAGNUM_TRADE_EXPORT SceneData { */ SceneField fieldName(UnsignedInt id) const; + /** + * @brief Field flags + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). + * @see @ref findFieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const + */ + SceneFieldFlags fieldFlags(UnsignedInt id) const; + /** * @brief Field type * @m_since_latest @@ -1380,8 +1498,12 @@ class MAGNUM_TRADE_EXPORT SceneData { * @ref mappingBound() and @p offset not larger than * @ref fieldSize(UnsignedInt) const. * - * The lookup is done in an @f$ \mathcal{O}(n) @f$ complexity with - * @f$ n @f$ being the size of the field. + * If the field has @ref SceneFieldFlag::ImplicitMapping, the lookup is + * done in an @f$ \mathcal{O}(1) @f$ complexity. Otherwise, if the + * field has @ref SceneFieldFlag::OrderedMapping, the lookup is done in + * an @f$ \mathcal{O}(\log{} n) @f$ complexity with @f$ n @f$ being the + * size of the field. Otherwise, the lookup is done in an + * @f$ \mathcal{O}(n) @f$ complexity. * * You can also use @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const * to directly find offset of an object in given named field. @@ -1399,8 +1521,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * exist, @p object is expected to be smaller than @ref mappingBound() * and @p offset not be larger than @ref fieldSize(SceneField) const. * - * The lookup is done in an @f$ \mathcal{O}(m + n) @f$ complexity with - * @f$ m @f$ being the field count and @f$ n @f$ the size of the field. + * If the field has @ref SceneFieldFlag::ImplicitMapping, the lookup is + * done in an @f$ \mathcal{O}(m) @f$ complexity with @f$ m @f$ being + * the * field count. Otherwise, if the field has + * @ref SceneFieldFlag::OrderedMapping, the lookup is done in an + * @f$ \mathcal{O}(m + \log{} n) @f$ complexity with @f$ m @f$ being + * the field count and @f$ n @f$ the size of the field. Otherwise, the + * lookup is done in an @f$ \mathcal{O}(m + n) @f$ complexity. * * @see @ref hasField(), @ref hasFieldObject(SceneField, UnsignedInt) const, * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const @@ -1449,6 +1576,15 @@ class MAGNUM_TRADE_EXPORT SceneData { */ bool hasFieldObject(SceneField fieldName, UnsignedInt object) const; + /** + * @brief Field flags + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + SceneFieldFlags fieldFlags(SceneField name) const; + /** * @brief Type of a named field * @m_since_latest @@ -2632,10 +2768,10 @@ class MAGNUM_TRADE_EXPORT SceneData { * the actual memory might be not. Additionally, the returned * @ref SceneFieldData instances may have different data pointers and * sizes than what's returned by the @ref field() and - * @ref fieldData(UnsignedInt) const accessors as some of them might be - * offset-only --- use this function only if you *really* know what are - * you doing. - * @see @ref fieldData(), @ref SceneFieldData::isOffsetOnly() + * @ref fieldData(UnsignedInt) const accessors as some of them might + * have @ref SceneFieldFlag::OffsetOnly --- use this function only if + * you *really* know what are you doing. + * @see @ref fieldData(), @ref SceneFieldData::flags() */ Containers::Array releaseFieldData(); @@ -2909,12 +3045,13 @@ namespace Implementation { } } -constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const UnsignedShort fieldArraySize) noexcept: +constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: _size{(CORRADE_CONSTEXPR_ASSERT(mappingData.size() == fieldData.size(), "Trade::SceneFieldData: expected mapping and field view to have the same size but got" << mappingData.size() << "and" << fieldData.size()), mappingData.size())}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, - _isOffsetOnly{false}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & SceneFieldFlag::OffsetOnly), + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view"), flags)}, _mappingType{mappingType}, _mappingStride{(CORRADE_CONSTEXPR_ASSERT(mappingData.stride() >= -32768 && mappingData.stride() <= 32767, "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got" << mappingData.stride()), Short(mappingData.stride()))}, @@ -2926,23 +3063,24 @@ constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappi "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, _fieldData{fieldData.data()} {} -template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, Implementation::SceneFieldTypeFor::type>::type(), fieldData, 0} {} +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, Implementation::SceneFieldTypeFor::type>::type(), fieldData, 0, flags} {} -template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView2D& fieldData) noexcept: SceneFieldData{ +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView2D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{ name, Implementation::sceneMappingTypeFor::type>(), mappingData, Implementation::SceneFieldTypeFor::type>::type(), Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, /* Not using isContiguous<1>() as that's not constexpr */ - (CORRADE_CONSTEXPR_ASSERT(fieldData.stride()[1] == sizeof(U), "Trade::SceneFieldData: second field view dimension is not contiguous"), UnsignedShort(fieldData.size()[1])) + (CORRADE_CONSTEXPR_ASSERT(fieldData.stride()[1] == sizeof(U), "Trade::SceneFieldData: second field view dimension is not contiguous"), UnsignedShort(fieldData.size()[1])), + flags } {} -constexpr SceneFieldData::SceneFieldData(const SceneField name, const std::size_t size, const SceneMappingType mappingType, const std::size_t mappingOffset, const std::ptrdiff_t mappingStride, const SceneFieldType fieldType, const std::size_t fieldOffset, const std::ptrdiff_t fieldStride, const UnsignedShort fieldArraySize) noexcept: +constexpr SceneFieldData::SceneFieldData(const SceneField name, const std::size_t size, const SceneMappingType mappingType, const std::size_t mappingOffset, const std::ptrdiff_t mappingStride, const SceneFieldType fieldType, const std::size_t fieldOffset, const std::ptrdiff_t fieldStride, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: _size{size}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, - _isOffsetOnly{true}, + _flags{flags|SceneFieldFlag::OffsetOnly}, _mappingType{mappingType}, _mappingStride{(CORRADE_CONSTEXPR_ASSERT(mappingStride >= -32768 && mappingStride <= 32767, "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got" << mappingStride), Short(mappingStride))}, diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index c3c987002..19a7978a0 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -63,6 +63,10 @@ struct SceneDataTest: TestSuite::Tester { void fieldTypeSizeAlignmentInvalid(); void debugFieldType(); + void debugFieldFlag(); + void debugFieldFlags(); + void debugFieldFlagsSupersets(); + void constructField(); void constructFieldDefault(); void constructFieldCustom(); @@ -79,6 +83,7 @@ struct SceneDataTest: TestSuite::Tester { void constructFieldInconsistentViewSize(); void constructFieldTooLargeMappingStride(); void constructFieldTooLargeFieldStride(); + void constructFieldOffsetOnlyNotAllowed(); void constructFieldWrongDataAccess(); void constructField2DWrongSize(); void constructField2DNonContiguous(); @@ -203,6 +208,57 @@ const struct { {"mutable", DataFlag::Mutable} }; +const struct { + const char* name; + SceneFieldFlags flags; + UnsignedInt mapping[5]; + UnsignedInt object; + UnsignedInt offset; + Containers::Optional expected; +} FindFieldObjectOffsetData[]{ + {"", {}, + {4, 2, 1, 0, 2}, 2, 0, 1}, + {"not found", {}, + {4, 2, 1, 0, 2}, 3, 0, Containers::NullOpt}, + {"offset", {}, + {4, 2, 1, 0, 2}, 2, 2, 4}, + {"offset, not found", {}, + {4, 2, 1, 0, 2}, 2, 5, Containers::NullOpt}, + + {"ordered", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 4, 0, 2}, + {"ordered, not found", SceneFieldFlag::OrderedMapping, + /* It *is* there but the binary search expects an ordered range and thus + should not even see it */ + {1, 3, 4, 4, 2}, 2, 0, Containers::NullOpt}, + {"ordered, not found, too small", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 0, 0, Containers::NullOpt}, + {"ordered, not found, too large", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 6, 0, Containers::NullOpt}, + {"ordered, offset", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 4, 3, 3}, + {"ordered, offset, not found", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 4, 4, Containers::NullOpt}, + + {"implicit", SceneFieldFlag::ImplicitMapping, + /* Not there but the assumption is that the ID matches the offset */ + {5, 5, 5, 5, 5}, 3, 0, 3}, + {"implicit, not found", SceneFieldFlag::ImplicitMapping, + /* Is there but the assumption is that the ID matches the offset, which + is out of bounds */ + {5, 5, 5, 5, 5}, 5, 0, Containers::NullOpt}, + {"implicit, offset", SceneFieldFlag::ImplicitMapping, + /* Not there but the assumption is that the ID matches the offset; + verifying that the offset is properly accounted for */ + {5, 5, 5, 5, 5}, 3, 3, 3}, + {"implicit, offset, not found, less than offset", SceneFieldFlag::ImplicitMapping, + /* Cerifying that the offset is properly accounted for -- it's never + found if offset > id */ + {5, 5, 5, 5, 5}, 3, 4, Containers::NullOpt}, + {"implicit, offset, not found, out of bounds", SceneFieldFlag::ImplicitMapping, + {5, 5, 5, 5, 5}, 5, 4, Containers::NullOpt} +}; + const struct { const char* name; std::size_t offset; @@ -298,6 +354,10 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::fieldTypeSizeAlignmentInvalid, &SceneDataTest::debugFieldType, + &SceneDataTest::debugFieldFlag, + &SceneDataTest::debugFieldFlags, + &SceneDataTest::debugFieldFlagsSupersets, + &SceneDataTest::constructField, &SceneDataTest::constructFieldDefault, &SceneDataTest::constructFieldCustom, @@ -314,6 +374,7 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructFieldInconsistentViewSize, &SceneDataTest::constructFieldTooLargeMappingStride, &SceneDataTest::constructFieldTooLargeFieldStride, + &SceneDataTest::constructFieldOffsetOnlyNotAllowed, &SceneDataTest::constructFieldWrongDataAccess, &SceneDataTest::constructField2DWrongSize, &SceneDataTest::constructField2DNonContiguous, @@ -353,12 +414,16 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructCopy, &SceneDataTest::constructMove, - &SceneDataTest::findFieldId, - &SceneDataTest::findFieldObjectOffset, - &SceneDataTest::findFieldObjectOffset, - &SceneDataTest::findFieldObjectOffset, - &SceneDataTest::findFieldObjectOffset, - &SceneDataTest::findFieldObjectOffsetInvalidOffset, + &SceneDataTest::findFieldId}); + + addInstancedTests({ + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset + }, Containers::arraySize(FindFieldObjectOffsetData)); + + addTests({&SceneDataTest::findFieldObjectOffsetInvalidOffset, &SceneDataTest::fieldObjectOffsetNotFound, &SceneDataTest::mappingAsArrayByIndex, @@ -645,6 +710,30 @@ void SceneDataTest::debugFieldType() { CORRADE_COMPARE(out.str(), "Trade::SceneFieldType::Matrix3x4h Trade::SceneFieldType(0xdead)\n"); } +void SceneDataTest::debugFieldFlag() { + std::ostringstream out; + + Debug(&out) << SceneFieldFlag::OffsetOnly << SceneFieldFlag(0xbe); + CORRADE_COMPARE(out.str(), "Trade::SceneFieldFlag::OffsetOnly Trade::SceneFieldFlag(0xbe)\n"); +} + +void SceneDataTest::debugFieldFlags() { + std::ostringstream out; + + Debug{&out} << (SceneFieldFlag::OffsetOnly|SceneFieldFlag(0xe0)) << SceneFieldFlags{}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldFlag::OffsetOnly|Trade::SceneFieldFlag(0xe0) Trade::SceneFieldFlags{}\n"); +} + +void SceneDataTest::debugFieldFlagsSupersets() { + /* ImplicitMapping is a superset of OrderedMapping, so only one should be + printed */ + { + std::ostringstream out; + Debug{&out} << (SceneFieldFlag::ImplicitMapping|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(out.str(), "Trade::SceneFieldFlag::ImplicitMapping\n"); + } +} + const UnsignedShort RotationMapping2D[3] { 17, 35, @@ -660,8 +749,8 @@ void SceneDataTest::constructField() { const UnsignedShort rotationMappingData[3]{}; const Complexd rotationFieldData[3]; - SceneFieldData rotations{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; - CORRADE_VERIFY(!rotations.isOffsetOnly()); + SceneFieldData rotations{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(rotations.flags(), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(rotations.name(), SceneField::Rotation); CORRADE_COMPARE(rotations.size(), 3); CORRADE_COMPARE(rotations.mappingType(), SceneMappingType::UnsignedShort); @@ -685,16 +774,16 @@ void SceneDataTest::constructField() { CORRADE_VERIFY(rotations.mappingData(someArray).data() == rotationMappingData); #ifndef CORRADE_MSVC2015_COMPATIBILITY /* Won't bother anymore */ - constexpr SceneFieldData crotations{SceneField::Rotation, Containers::arrayView(RotationMapping2D), Containers::arrayView(RotationField2D)}; - constexpr bool isOffsetOnly = crotations.isOffsetOnly(); + constexpr SceneFieldData crotations{SceneField::Rotation, Containers::arrayView(RotationMapping2D), Containers::arrayView(RotationField2D), SceneFieldFlag::ImplicitMapping}; constexpr SceneField name = crotations.name(); + constexpr SceneFieldFlags flags = crotations.flags(); constexpr SceneMappingType mappingType = crotations.mappingType(); constexpr Containers::StridedArrayView1D mappingData = crotations.mappingData(); constexpr SceneFieldType fieldType = crotations.fieldType(); constexpr UnsignedShort fieldArraySize = crotations.fieldArraySize(); constexpr Containers::StridedArrayView1D fieldData = crotations.fieldData(); - CORRADE_VERIFY(!isOffsetOnly); CORRADE_COMPARE(name, SceneField::Rotation); + CORRADE_COMPARE(flags, SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedShort); CORRADE_COMPARE(mappingData.size(), 3); CORRADE_COMPARE(mappingData.stride(), sizeof(UnsignedShort)); @@ -739,8 +828,8 @@ void SceneDataTest::constructField2D() { auto rotationMappingView = Containers::StridedArrayView2D{rotationMappingData, {6, sizeof(UnsignedShort)}}.every(2); auto rotationFieldView = Containers::StridedArrayView2D{rotationFieldData, {6, sizeof(Complexd)}}.every(2); - SceneFieldData rotations{SceneField::Rotation, rotationMappingView, SceneFieldType::Complexd, rotationFieldView}; - CORRADE_VERIFY(!rotations.isOffsetOnly()); + SceneFieldData rotations{SceneField::Rotation, rotationMappingView, SceneFieldType::Complexd, rotationFieldView, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(rotations.flags(), SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(rotations.name(), SceneField::Rotation); CORRADE_COMPARE(rotations.size(), 3); CORRADE_COMPARE(rotations.mappingType(), SceneMappingType::UnsignedShort); @@ -757,8 +846,8 @@ void SceneDataTest::constructField2D() { void SceneDataTest::constructFieldTypeErased() { const UnsignedLong scalingMappingData[3]{}; const Vector3 scalingFieldData[3]; - SceneFieldData scalings{SceneField::Scaling, SceneMappingType::UnsignedLong, Containers::arrayCast(Containers::stridedArrayView(scalingMappingData)), SceneFieldType::Vector3, Containers::arrayCast(Containers::stridedArrayView(scalingFieldData))}; - CORRADE_VERIFY(!scalings.isOffsetOnly()); + SceneFieldData scalings{SceneField::Scaling, SceneMappingType::UnsignedLong, Containers::arrayCast(Containers::stridedArrayView(scalingMappingData)), SceneFieldType::Vector3, Containers::arrayCast(Containers::stridedArrayView(scalingFieldData)), SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(scalings.flags(), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scalings.name(), SceneField::Scaling); CORRADE_COMPARE(scalings.size(), 3); CORRADE_COMPARE(scalings.mappingType(), SceneMappingType::UnsignedLong); @@ -789,8 +878,8 @@ void SceneDataTest::constructFieldOffsetOnly() { {0, 15, {67.0f, -1.1f}} }; - SceneFieldData a{SceneField::Translation, 2, SceneMappingType::UnsignedShort, offsetof(Data, object), sizeof(Data), SceneFieldType::Vector2, offsetof(Data, translation), sizeof(Data)}; - CORRADE_VERIFY(a.isOffsetOnly()); + SceneFieldData a{SceneField::Translation, 2, SceneMappingType::UnsignedShort, offsetof(Data, object), sizeof(Data), SceneFieldType::Vector2, offsetof(Data, translation), sizeof(Data), SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(a.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(a.name(), SceneField::Translation); CORRADE_COMPARE(a.size(), 2); CORRADE_COMPARE(a.mappingType(), SceneMappingType::UnsignedShort); @@ -814,8 +903,8 @@ constexpr Int ArrayOffsetFieldData[3*4]{}; void SceneDataTest::constructFieldArray() { UnsignedByte offsetMappingData[3]; Int offsetFieldData[3*4]; - SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetMappingData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}}; - CORRADE_VERIFY(!data.isOffsetOnly()); + SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetMappingData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); CORRADE_COMPARE(data.size(), 3); CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); @@ -828,8 +917,8 @@ void SceneDataTest::constructFieldArray() { CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); CORRADE_VERIFY(data.fieldData().data() == offsetFieldData); - constexpr SceneFieldData cdata{sceneFieldCustom(34), Containers::arrayView(ArrayOffsetMappingData), Containers::StridedArrayView2D{ArrayOffsetFieldData, {3, 4}}}; - CORRADE_VERIFY(!cdata.isOffsetOnly()); + constexpr SceneFieldData cdata{sceneFieldCustom(34), Containers::arrayView(ArrayOffsetMappingData), Containers::StridedArrayView2D{ArrayOffsetFieldData, {3, 4}}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(cdata.flags(), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(cdata.name(), sceneFieldCustom(34)); CORRADE_COMPARE(cdata.size(), 3); CORRADE_COMPARE(cdata.mappingType(), SceneMappingType::UnsignedByte); @@ -846,8 +935,8 @@ void SceneDataTest::constructFieldArray() { void SceneDataTest::constructFieldArray2D() { char offsetMappingData[3*sizeof(UnsignedByte)]; char offsetFieldData[3*4*sizeof(Int)]; - SceneFieldData data{sceneFieldCustom(34), Containers::StridedArrayView2D{offsetMappingData, {3, sizeof(UnsignedByte)}}, SceneFieldType::Int, Containers::StridedArrayView2D{offsetFieldData, {3, 4*sizeof(Int)}}, 4}; - CORRADE_VERIFY(!data.isOffsetOnly()); + SceneFieldData data{sceneFieldCustom(34), Containers::StridedArrayView2D{offsetMappingData, {3, sizeof(UnsignedByte)}}, SceneFieldType::Int, Containers::StridedArrayView2D{offsetFieldData, {3, 4*sizeof(Int)}}, 4, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); CORRADE_COMPARE(data.size(), 3); CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); @@ -865,8 +954,8 @@ void SceneDataTest::constructFieldArrayTypeErased() { UnsignedByte offsetMappingData[3]; Int offsetFieldData[3*4]; Containers::StridedArrayView1D offset{offsetFieldData, 3, 4*sizeof(Int)}; - SceneFieldData data{sceneFieldCustom(34), SceneMappingType::UnsignedByte, Containers::arrayCast(Containers::stridedArrayView(offsetMappingData)), SceneFieldType::Int, Containers::arrayCast(offset), 4}; - CORRADE_VERIFY(!data.isOffsetOnly()); + SceneFieldData data{sceneFieldCustom(34), SceneMappingType::UnsignedByte, Containers::arrayCast(Containers::stridedArrayView(offsetMappingData)), SceneFieldType::Int, Containers::arrayCast(offset), 4, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); CORRADE_COMPARE(data.size(), 3); CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); @@ -887,8 +976,8 @@ void SceneDataTest::constructFieldArrayOffsetOnly() { Int offset[4]; }; - SceneFieldData data{sceneFieldCustom(34), 3, SceneMappingType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4}; - CORRADE_VERIFY(data.isOffsetOnly()); + SceneFieldData data{sceneFieldCustom(34), 3, SceneMappingType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); CORRADE_COMPARE(data.size(), 3); CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); @@ -903,8 +992,8 @@ void SceneDataTest::constructFieldArrayOffsetOnly() { CORRADE_COMPARE(data.mappingData(actual).stride(), sizeof(Data)); CORRADE_VERIFY(data.mappingData(actual).data() == &actual[0].object); - constexpr SceneFieldData cdata{sceneFieldCustom(34), 3, SceneMappingType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4}; - CORRADE_VERIFY(cdata.isOffsetOnly()); + constexpr SceneFieldData cdata{sceneFieldCustom(34), 3, SceneMappingType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(cdata.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(cdata.name(), sceneFieldCustom(34)); CORRADE_COMPARE(cdata.size(), 3); CORRADE_COMPARE(cdata.mappingType(), SceneMappingType::UnsignedByte); @@ -997,6 +1086,24 @@ void SceneDataTest::constructFieldTooLargeFieldStride() { "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got -32769\n"); } +void SceneDataTest::constructFieldOffsetOnlyNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationMappingData[3]{}; + const Quaternion rotationFieldData[3]; + + /* This one is fine */ + SceneFieldData{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion), SceneFieldFlag::OffsetOnly}; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OffsetOnly}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view\n"); +} + void SceneDataTest::constructFieldWrongDataAccess() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -1006,8 +1113,8 @@ void SceneDataTest::constructFieldWrongDataAccess() { const Quaternion rotationFieldData[3]; SceneFieldData a{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; SceneFieldData b{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion)}; - CORRADE_VERIFY(!a.isOffsetOnly()); - CORRADE_VERIFY(b.isOffsetOnly()); + CORRADE_COMPARE(a.flags(), SceneFieldFlags{}); + CORRADE_COMPARE(b.flags(), SceneFieldFlag::OffsetOnly); a.mappingData(rotationMappingData); /* This is fine, no asserts */ a.fieldData(rotationFieldData); @@ -1229,11 +1336,11 @@ void SceneDataTest::construct() { SceneFieldType::Int, offsetof(TransformParent, parent), sizeof(TransformParent)}; SceneFieldData meshes{SceneField::Mesh, materialMeshRadiusMappingData, - meshFieldData}; + meshFieldData, SceneFieldFlag::OrderedMapping}; /* Custom & array */ SceneFieldData radiuses{sceneFieldCustom(37), materialMeshRadiusMappingData, - Containers::arrayCast<2, Float>(radiusFieldData)}; + Containers::arrayCast<2, Float>(radiusFieldData), SceneFieldFlag::OrderedMapping}; SceneData scene{SceneMappingType::UnsignedShort, 8, std::move(data), { transformations, parents, meshes, radiuses }, &importerState}; @@ -1256,6 +1363,10 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.fieldName(1), SceneField::Parent); CORRADE_COMPARE(scene.fieldName(2), SceneField::Mesh); CORRADE_COMPARE(scene.fieldName(3), sceneFieldCustom(37)); + CORRADE_COMPARE(scene.fieldFlags(0), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldFlags(1), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(scene.fieldFlags(2), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldFlags(3), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::Matrix4x4); CORRADE_COMPARE(scene.fieldType(1), SceneFieldType::Int); CORRADE_COMPARE(scene.fieldType(2), SceneFieldType::UnsignedByte); @@ -1275,14 +1386,14 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.fieldData(2).mappingType(), SceneMappingType::UnsignedShort); CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(2).mappingData())[1], 6); CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(2).fieldData())[1], 7); - CORRADE_VERIFY(!scene.fieldData(2).isOffsetOnly()); + CORRADE_COMPARE(scene.fieldData(2).flags(), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scene.fieldData(2).fieldType(), SceneFieldType::UnsignedByte); CORRADE_COMPARE(scene.fieldData(2).fieldArraySize(), 0); /* Offset-only */ CORRADE_COMPARE(scene.fieldData(1).name(), SceneField::Parent); CORRADE_COMPARE(scene.fieldData(1).size(), 5); CORRADE_COMPARE(scene.fieldData(1).mappingType(), SceneMappingType::UnsignedShort); - CORRADE_VERIFY(!scene.fieldData(1).isOffsetOnly()); + CORRADE_COMPARE(scene.fieldData(1).flags(), SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldData(1).fieldType(), SceneFieldType::Int); CORRADE_COMPARE(scene.fieldData(1).fieldArraySize(), 0); CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(1).mappingData())[4], 1); @@ -1291,7 +1402,7 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.fieldData(3).name(), sceneFieldCustom(37)); CORRADE_COMPARE(scene.fieldData(3).size(), 2); CORRADE_COMPARE(scene.fieldData(3).mappingType(), SceneMappingType::UnsignedShort); - CORRADE_VERIFY(!scene.fieldData(3).isOffsetOnly()); + CORRADE_COMPARE(scene.fieldData(3).flags(), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scene.fieldData(3).fieldType(), SceneFieldType::Float); CORRADE_COMPARE(scene.fieldData(3).fieldArraySize(), 2); CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(3).mappingData())[0], 2); @@ -1374,6 +1485,10 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.mutableField(3)[0][1], 1.5f); /* Field property access by name */ + CORRADE_COMPARE(scene.fieldFlags(SceneField::Transformation), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldFlags(SceneField::Parent), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(scene.fieldFlags(SceneField::Mesh), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldFlags(sceneFieldCustom(37)), SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scene.fieldType(SceneField::Transformation), SceneFieldType::Matrix4x4); CORRADE_COMPARE(scene.fieldType(SceneField::Parent), SceneFieldType::Int); CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedByte); @@ -1608,6 +1723,7 @@ void SceneDataTest::constructDeprecated() { CORRADE_COMPARE(scene.importerState(), &a); CORRADE_COMPARE(scene.fieldCount(), 1); CORRADE_COMPARE(scene.fieldName(0), SceneField::Parent); + CORRADE_COMPARE(scene.fieldFlags(0), SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::Int); if(data.is2D || data.is3D) { CORRADE_COMPARE_AS(scene.mapping(0), @@ -2113,60 +2229,46 @@ void SceneDataTest::findFieldId() { template void SceneDataTest::findFieldObjectOffset() { setTestCaseTemplateName(NameTraits::name()); - /** @todo update once field flags describing object order are present */ + auto&& data = FindFieldObjectOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); struct Field { T object; UnsignedInt mesh; - } fields[]{ - {4, 1}, - {1, 3}, - {2, 4}, - {0, 5}, - {2, 5} + } fields[5]{ + {T(data.mapping[0]), 0}, + {T(data.mapping[1]), 0}, + {T(data.mapping[2]), 0}, + {T(data.mapping[3]), 0}, + {T(data.mapping[4]), 0} }; Containers::StridedArrayView1D view = fields; SceneData scene{Implementation::sceneMappingTypeFor(), 7, {}, fields, { /* Test also with a completely empty field */ SceneFieldData{SceneField::Parent, Implementation::sceneMappingTypeFor(), nullptr, SceneFieldType::Int, nullptr}, - SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh), data.flags} }}; - CORRADE_COMPARE(scene.findFieldObjectOffset(0, 4), Containers::NullOpt); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Parent, 4), Containers::NullOpt); - CORRADE_COMPARE(scene.findFieldObjectOffset(1, 4), 0); - CORRADE_COMPARE(scene.findFieldObjectOffset(1, 1), 1); - CORRADE_COMPARE(scene.findFieldObjectOffset(1, 2), 2); - CORRADE_COMPARE(scene.findFieldObjectOffset(1, 2, 3), 4); - CORRADE_COMPARE(scene.findFieldObjectOffset(1, 2, 5), Containers::NullOpt); - CORRADE_COMPARE(scene.findFieldObjectOffset(1, 3), Containers::NullOpt); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 4), 0); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 1), 1); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 2), 2); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 2, 3), 4); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 2, 5), Containers::NullOpt); - CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 3), Containers::NullOpt); - - CORRADE_COMPARE(scene.fieldObjectOffset(1, 4), 0); - CORRADE_COMPARE(scene.fieldObjectOffset(1, 1), 1); - CORRADE_COMPARE(scene.fieldObjectOffset(1, 2), 2); - CORRADE_COMPARE(scene.fieldObjectOffset(1, 2, 3), 4); - CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 4), 0); - CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 1), 1); - CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 2), 2); - CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 2, 3), 4); - - CORRADE_VERIFY(!scene.hasFieldObject(0, 4)); - CORRADE_VERIFY(!scene.hasFieldObject(SceneField::Parent, 4)); - CORRADE_VERIFY(scene.hasFieldObject(1, 4)); - CORRADE_VERIFY(scene.hasFieldObject(1, 1)); - CORRADE_VERIFY(scene.hasFieldObject(1, 2)); - CORRADE_VERIFY(!scene.hasFieldObject(1, 3)); - CORRADE_VERIFY(scene.hasFieldObject(SceneField::Mesh, 4)); - CORRADE_VERIFY(scene.hasFieldObject(SceneField::Mesh, 1)); - CORRADE_VERIFY(scene.hasFieldObject(SceneField::Mesh, 2)); - CORRADE_VERIFY(!scene.hasFieldObject(SceneField::Mesh, 3)); + /* An empty field should not find anything for any query with any flags */ + if(data.offset == 0) { + CORRADE_COMPARE(scene.findFieldObjectOffset(0, data.object), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Parent, data.object), Containers::NullOpt); + CORRADE_VERIFY(!scene.hasFieldObject(0, data.object)); + CORRADE_VERIFY(!scene.hasFieldObject(SceneField::Parent, data.object)); + } + + CORRADE_COMPARE(scene.findFieldObjectOffset(1, data.object, data.offset), data.expected); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, data.object, data.offset), data.expected); + if(data.offset == 0) { + CORRADE_COMPARE(scene.hasFieldObject(1, data.object), !!data.expected); + CORRADE_COMPARE(scene.hasFieldObject(SceneField::Mesh, data.object), !!data.expected); + } + + if(data.expected) { + CORRADE_COMPARE(scene.fieldObjectOffset(1, data.object, data.offset), *data.expected); + CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, data.object, data.offset), *data.expected); + } } void SceneDataTest::findFieldObjectOffsetInvalidOffset() { @@ -4537,6 +4639,7 @@ void SceneDataTest::fieldNotFound() { scene.hasFieldObject(2, 0); scene.fieldData(2); scene.fieldName(2); + scene.fieldFlags(2); scene.fieldType(2); scene.fieldSize(2); scene.fieldArraySize(2); @@ -4548,6 +4651,7 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(2); scene.fieldId(sceneFieldCustom(666)); + scene.fieldFlags(sceneFieldCustom(666)); scene.findFieldObjectOffset(sceneFieldCustom(666), 0); scene.fieldObjectOffset(sceneFieldCustom(666), 0); scene.hasFieldObject(sceneFieldCustom(666), 0); @@ -4597,6 +4701,7 @@ void SceneDataTest::fieldNotFound() { "Trade::SceneData::hasFieldObject(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldData(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldName(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldFlags(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldType(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldSize(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldArraySize(): index 2 out of range for 2 fields\n" @@ -4608,6 +4713,7 @@ void SceneDataTest::fieldNotFound() { "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldId(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldFlags(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::findFieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::fieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::hasFieldObject(): field Trade::SceneField::Custom(666) not found\n" diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index af0411e4a..dde9a1df0 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -103,6 +103,8 @@ class TextureData; enum class SceneMappingType: UnsignedByte; enum class SceneField: UnsignedInt; enum class SceneFieldType: UnsignedShort; +enum class SceneFieldFlag: UnsignedByte; +typedef Containers::EnumSet SceneFieldFlags; class SceneFieldData; class SceneData;