diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 28c8388cf..b0b566329 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -147,6 +148,7 @@ Debug& operator<<(Debug& debug, const SceneFieldType value) { switch(value) { /* LCOV_EXCL_START */ #define _c(value) case SceneFieldType::value: return debug << (debug.immediateFlags() & Debug::Flag::Packed ? "" : "::") << Debug::nospace << #value; + _c(Bit) _c(Float) _c(Half) _c(Double) @@ -265,6 +267,8 @@ UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { #pragma GCC diagnostic error "-Wswitch" #endif switch(type) { + case SceneFieldType::Bit: + CORRADE_ASSERT_UNREACHABLE("Trade::sceneFieldTypeSize(): can't use with" << SceneFieldType::Bit, {}); case SceneFieldType::UnsignedByte: case SceneFieldType::Byte: case SceneFieldType::StringOffset8: @@ -402,6 +406,8 @@ UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { #pragma GCC diagnostic error "-Wswitch" #endif switch(type) { + case SceneFieldType::Bit: + CORRADE_ASSERT_UNREACHABLE("Trade::sceneFieldTypeAlignment(): can't use with" << SceneFieldType::Bit, {}); case SceneFieldType::UnsignedByte: case SceneFieldType::Vector2ub: case SceneFieldType::Vector3ub: @@ -551,6 +557,11 @@ Debug& operator<<(Debug& debug, const SceneFieldFlags value) { } 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} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* This caused an assertion in the delegated-to constructor, bail instead + of cascading the asserts further */ + if(fieldType == SceneFieldType::Bit) return; + #endif /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ @@ -575,6 +586,38 @@ SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedA CORRADE_ASSERT(fieldData.isContiguous<1>(), "Trade::SceneFieldData: second field view dimension is not contiguous", ); } +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const void* fieldData, UnsignedByte fieldBitOffset, std::size_t fieldSize, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize, SceneFieldFlags flags) noexcept: SceneFieldData{name, {}, Containers::StridedArrayView1D{{mappingData.data(), ~std::size_t{}}, mappingData.size()[0], mappingData.stride()[0]}, fieldData, fieldBitOffset, fieldSize, fieldStride, 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 */ + + if(mappingData.size()[1] == 8) + _mappingTypeStringType = UnsignedByte(SceneMappingType::UnsignedLong); + else if(mappingData.size()[1] == 4) + _mappingTypeStringType = UnsignedByte(SceneMappingType::UnsignedInt); + else if(mappingData.size()[1] == 2) + _mappingTypeStringType = UnsignedByte(SceneMappingType::UnsignedShort); + else if(mappingData.size()[1] == 1) + _mappingTypeStringType = UnsignedByte(SceneMappingType::UnsignedByte); + else CORRADE_ASSERT_UNREACHABLE("Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got" << mappingData.size()[1], ); + + CORRADE_ASSERT(mappingData.isContiguous<1>(), "Trade::SceneFieldData: second mapping view dimension is not contiguous", ); +} + +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const Containers::StridedBitArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingData, fieldData.data(), UnsignedByte(fieldData.offset()), fieldData.size(), fieldData.stride(), 0, flags} {} + +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const Containers::StridedBitArrayView2D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingData, fieldData.data(), UnsignedByte(fieldData.offset()), fieldData.size()[0], fieldData.stride()[0], + /* There's no need to check that the array size fits into 16 bits, as the + stronger requirement is that the signed stride fits into 16 bits */ + UnsignedShort(fieldData.size()[1]), + 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 */ + CORRADE_ASSERT(fieldData.isContiguous<1>(), "Trade::SceneFieldData: second field view dimension is not contiguous", ); +} + SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const char* const stringData, const SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: _size{mappingData.size()}, _name{name}, @@ -647,6 +690,8 @@ Containers::StridedArrayView1D SceneFieldData::mappingData(const Con Containers::StridedArrayView1D SceneFieldData::fieldData() const { CORRADE_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array", {}); + CORRADE_ASSERT(fieldType() != SceneFieldType::Bit, + "Trade::SceneFieldData::fieldData(): the field is" << SceneFieldType::Bit << Debug::nospace << ", use fieldBitData() instead", {}); return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ @@ -654,12 +699,37 @@ Containers::StridedArrayView1D SceneFieldData::fieldData() const { } Containers::StridedArrayView1D SceneFieldData::fieldData(const Containers::ArrayView data) const { + CORRADE_ASSERT(fieldType() != SceneFieldType::Bit, + "Trade::SceneFieldData::fieldData(): the field is" << SceneFieldType::Bit << Debug::nospace << ", use fieldBitData() instead", {}); return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ data, _flags & SceneFieldFlag::OffsetOnly ? static_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _field.data.stride}; } +Containers::StridedBitArrayView2D SceneFieldData::fieldBitData() const { + CORRADE_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), + "Trade::SceneFieldData::fieldBitData(): the field is offset-only, supply a data array", {}); + CORRADE_ASSERT(fieldType() == SceneFieldType::Bit, + "Trade::SceneFieldData::fieldBitData(): the field is" << fieldType() << Debug::nospace << ", not a bit", {}); + return Containers::StridedBitArrayView2D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedBitArrayView API? */ + {_fieldData.pointer, _field.data.bitOffset, ~std::size_t{} >> 3}, + {std::size_t(_size), _field.data.arraySize ? _field.data.arraySize : 1}, + {_field.data.stride, 1}}; +} + +Containers::StridedBitArrayView2D SceneFieldData::fieldBitData(const Containers::ArrayView data) const { + CORRADE_ASSERT(fieldType() == SceneFieldType::Bit, + "Trade::SceneFieldData::fieldBitData(): the field is" << fieldType() << Debug::nospace << ", not a bit", {}); + return Containers::StridedBitArrayView2D{ + {data, 0, data.size()*8}, + _flags & SceneFieldFlag::OffsetOnly ? static_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _field.data.bitOffset, + {std::size_t(_size), _field.data.arraySize ? _field.data.arraySize : 1}, + {_field.data.stride, 1}}; +} + namespace { /* https://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend ; @@ -770,28 +840,50 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp accessing the memory would be invalid anyway and enforcing this would lead to unnecessary friction with optional fields. */ if(field._size) { + /* Sizes, strides and array sizes are in bits for + SceneFieldType::Bit, additionally bit fields contain also bit + offset in the first byte */ + const bool isBitField = field.fieldType() == SceneFieldType::Bit; const UnsignedShort fieldArraySize = field.fieldArraySize(); - const UnsignedInt fieldTypeSize = sceneFieldTypeSize(field.fieldType())*(fieldArraySize ? fieldArraySize : 1); - - /* Both pointer and offset-only rely on basically same calculation, - do it with offsets in a single place and only interpret as - pointers in the non-offset-only check. Yes, yes, this may read - the `pointer` union member through `offset`. */ - std::size_t mappingBegin = field._mappingData.offset; - std::size_t fieldBegin = field._fieldData.offset; - std::size_t mappingEnd = mappingBegin + (field._size - 1)*field._mappingStride; - std::size_t fieldEnd = fieldBegin + (field._size - 1)*field._field.data.stride; - /* Flip for negative strides */ - if(mappingBegin > mappingEnd) std::swap(mappingBegin, mappingEnd); - if(fieldBegin > fieldEnd) std::swap(fieldBegin, fieldEnd); - /* Add the last element size to the higher address */ - mappingEnd += mappingTypeSize; - fieldEnd += fieldTypeSize; + const UnsignedInt fieldTypeSize = (isBitField ? 1 : sceneFieldTypeSize(field.fieldType()))*(fieldArraySize ? fieldArraySize : 1); + /* For negative strides the size is negative */ + const std::ptrdiff_t signedMappingSize = (field._size - 1)*field._mappingStride; + const std::ptrdiff_t signedFieldSize = (isBitField ? field._field.data.bitOffset : 0) + (field._size - 1)*field._field.data.stride; + + /* Calculate begin and end offsets. Both pointer and offset-only + rely on basically same calculation, do it with offsets in a + single place and only interpret as pointers in the + non-offset-only check. */ + std::ptrdiff_t mappingBegin = 0; + std::ptrdiff_t mappingEnd = mappingTypeSize; + std::ptrdiff_t fieldBegin = 0; + std::ptrdiff_t fieldEnd = fieldTypeSize; + /* For negative strides the begin pointer is moved backwards */ + if(field._mappingStride < 0) + mappingBegin += signedMappingSize; + else + mappingEnd += signedMappingSize; + if(field._field.data.stride < 0) + fieldBegin += signedFieldSize; + else + fieldEnd += signedFieldSize; + /* For bit fields round the offset to whole bytes -- begin down, + end up */ + if(isBitField) { + fieldBegin = fieldBegin/8; + fieldEnd = (fieldEnd + 7)/8; + } + /* Add the base data offset to both begin and end. Yes, yes, this + may read the `pointer` union member through `offset`. */ + mappingBegin += field._mappingData.offset; + mappingEnd += field._mappingData.offset; + fieldBegin += field._fieldData.offset; + fieldEnd += field._fieldData.offset; if(field._flags & SceneFieldFlag::OffsetOnly) { - CORRADE_ASSERT(mappingEnd <= _data.size(), + CORRADE_ASSERT(std::size_t(mappingEnd) <= _data.size(), "Trade::SceneData: offset-only mapping data of field" << i << "span" << mappingEnd << "bytes but passed data array has only" << _data.size(), ); - CORRADE_ASSERT(fieldEnd <= _data.size(), + CORRADE_ASSERT(std::size_t(fieldEnd) <= _data.size(), "Trade::SceneData: offset-only field data of field" << i << "span" << fieldEnd << "bytes but passed data array has only" << _data.size(), ); } else { CORRADE_ASSERT(reinterpret_cast(mappingBegin) >= _data.begin() && reinterpret_cast(mappingEnd) <= _data.end(), @@ -1045,6 +1137,8 @@ SceneFieldData SceneData::fieldData(const UnsignedInt id) const { const SceneFieldFlags flags = field._flags & ~SceneFieldFlag::OffsetOnly; if(field._mappingTypeStringType & Implementation::SceneMappingStringTypeMask) return SceneFieldData{field._name, field.mappingType(), fieldDataMappingViewInternal(field), field.stringData(_data), field.fieldType(), fieldDataFieldViewInternal(field), flags}; + else if(field._field.data.type == SceneFieldType::Bit) + return SceneFieldData{field._name, field.mappingType(), fieldDataMappingViewInternal(field), field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : field._fieldData.pointer, field._field.data.bitOffset, std::size_t(field._size), field._field.data.stride, field._field.data.arraySize, flags}; else return SceneFieldData{field._name, field.mappingType(), fieldDataMappingViewInternal(field), field._field.data.type, fieldDataFieldViewInternal(field), field._field.data.arraySize, flags}; } @@ -1303,6 +1397,8 @@ Containers::StridedArrayView2D SceneData::field(const UnsignedInt id CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::field(): index" << id << "out of range for" << _fields.size() << "fields", {}); const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field.fieldType() != SceneFieldType::Bit, + "Trade::SceneData::field():" << field._name << "is" << field.fieldType() << Debug::nospace << ", use fieldBits() or fieldBitArrays() to access it", {}); const UnsignedShort fieldArraySize = field.fieldArraySize(); /* Build a 2D view using information about mapping type size */ return Containers::arrayCast<2, const char>( @@ -1316,6 +1412,8 @@ Containers::StridedArrayView2D SceneData::mutableField(const UnsignedInt i CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::mutableField(): index" << id << "out of range for" << _fields.size() << "fields", {}); const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field.fieldType() != SceneFieldType::Bit, + "Trade::SceneData::mutableField():" << field._name << "is" << field.fieldType() << Debug::nospace << ", use mutableFieldBits() or mutableFieldBitArrays() to access it", {}); const UnsignedShort fieldArraySize = field.fieldArraySize(); /* Build a 2D view using information about attribute type size */ const auto out = Containers::arrayCast<2, const char>( @@ -1343,6 +1441,100 @@ Containers::StridedArrayView2D SceneData::mutableField(const SceneField na return mutableField(fieldId); } +Containers::StridedBitArrayView1D SceneData::fieldBits(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldBits(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field.fieldType() == SceneFieldType::Bit, + "Trade::SceneData::fieldBits():" << field._name << "is" << field._field.data.type << Debug::nospace << ", not a bit", {}); + CORRADE_ASSERT(!field.fieldArraySize(), + "Trade::SceneData::fieldBits():" << field._name << "is an array field, use fieldBitArrays() to access it", {}); + /** @todo this is duplicated four times with a few variants in the + fieldBits() overloads below plus once more in fieldData(), any way to + deduplicate this without writing an equally nasty helper or using + expensive transposes to get a 1D array from 2D? */ + return Containers::StridedBitArrayView1D{ + {_data.data(), 0, _data.size()*8}, + field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : field._fieldData.pointer, + field._field.data.bitOffset, + field._size, field._field.data.stride}; +} + +Containers::StridedBitArrayView2D SceneData::fieldBitArrays(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldBitArrays(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field.fieldType() == SceneFieldType::Bit, + "Trade::SceneData::fieldBitArrays():" << field._name << "is" << field._field.data.type << Debug::nospace << ", not a bit", {}); + return Containers::StridedBitArrayView2D{ + {_data.data(), 0, _data.size()*8}, + field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : field._fieldData.pointer, + field._field.data.bitOffset, + {std::size_t(field._size), field._field.data.arraySize ? field._field.data.arraySize : 1}, + {field._field.data.stride, 1}}; +} + +Containers::MutableStridedBitArrayView1D SceneData::mutableFieldBits(const UnsignedInt id) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableFieldBits(): data not mutable", {}); + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::mutableFieldBits(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field.fieldType() == SceneFieldType::Bit, + "Trade::SceneData::mutableFieldBits():" << field._name << "is" << field._field.data.type << Debug::nospace << ", not a bit", {}); + CORRADE_ASSERT(!field.fieldArraySize(), + "Trade::SceneData::mutableFieldBits():" << field._name << "is an array field, use fieldBitArrays() to access it", {}); + return Containers::MutableStridedBitArrayView1D{ + {_data.data(), 0, _data.size()*8}, + field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : static_cast(const_cast(field._fieldData.pointer)), + field._field.data.bitOffset, + field._size, field._field.data.stride}; +} + +Containers::MutableStridedBitArrayView2D SceneData::mutableFieldBitArrays(const UnsignedInt id) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableFieldBitArrays(): data not mutable", {}); + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::mutableFieldBitArrays(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field.fieldType() == SceneFieldType::Bit, + "Trade::SceneData::mutableFieldBitArrays():" << field._name << "is" << field._field.data.type << Debug::nospace << ", not a bit", {}); + return Containers::MutableStridedBitArrayView2D{ + {_data.data(), 0, _data.size()*8}, + field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : static_cast(const_cast(field._fieldData.pointer)), + field._field.data.bitOffset, + {std::size_t(field._size), field._field.data.arraySize ? field._field.data.arraySize : 1}, + {field._field.data.stride, 1}}; +} + +Containers::StridedBitArrayView1D SceneData::fieldBits(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::fieldBits(): field" << name << "not found", {}); + return fieldBits(fieldId); +} + +Containers::StridedBitArrayView2D SceneData::fieldBitArrays(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::fieldBitArrays(): field" << name << "not found", {}); + return fieldBitArrays(fieldId); +} + +Containers::MutableStridedBitArrayView1D SceneData::mutableFieldBits(const SceneField name) { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::mutableFieldBits(): field" << name << "not found", {}); + return mutableFieldBits(fieldId); +} + +Containers::MutableStridedBitArrayView2D SceneData::mutableFieldBitArrays(const SceneField name) { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::mutableFieldBitArrays(): field" << name << "not found", {}); + return mutableFieldBitArrays(fieldId); +} + const char* SceneData::fieldDataStringDataInternal(const SceneFieldData& field) const { return static_cast(field._flags & SceneFieldFlag::OffsetOnly ? _data.data() : field._fieldData.pointer) + extractStringFieldOffset(field._field.strideOffset); } diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 206f4167a..067c224dd 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -397,9 +397,13 @@ enum class SceneFieldType: UnsignedShort { /* 1-12 used by String* types defined below as they need to fit into 4 bits to be stored in a single byte together with SceneMappingType */ - /* 13 reserved for Bit, which needs StridedBitArrayView first */ + /** + * A single bit or an array of bits. Use @ref SceneData::fieldBits() or + * @relativeref{SceneData,fieldBitArrays()} for convenient access. + */ + Bit = 13, - Float = 14, /**< @relativeref{Magnum,Float} */ + Float, /**< @relativeref{Magnum,Float} */ Half, /**< @relativeref{Magnum,Half} */ Double, /**< @relativeref{Magnum,Double} */ UnsignedByte, /**< @relativeref{Magnum,UnsignedByte} */ @@ -685,6 +689,8 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldType value); @brief Size of given scene field type @m_since_latest +Expects that @p type isn't @ref SceneFieldType::Bit, for which the size isn't +representable in bytes. @see @ref sceneFieldTypeAlignment() */ MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeSize(SceneFieldType type); @@ -693,6 +699,8 @@ MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeSize(SceneFieldType type); @brief Alignment of given scene field type @m_since_latest +Expects that @p type isn't @ref SceneFieldType::Bit, for which the alignment +isn't representable in bytes. @see @ref sceneFieldTypeSize() */ MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeAlignment(SceneFieldType type); @@ -835,6 +843,16 @@ field specifying data for object @cpp 0 @ce, second entry for object with @ref SceneFieldFlag::ImplicitMapping, which is a superset of @relativeref{SceneFieldFlag,OrderedMapping}. +@subsection Trade-SceneFieldData-usage-bits Bit fields + +Bit fields have dedicated constructors taking a +@ref Containers::StridedBitArrayView1D or +@ref Containers::StridedBitArrayView2D, and because the type is always +@ref SceneFieldType::Bit, it's omitted. For offset-only bit fields there's a +@ref SceneFieldData(SceneField, std::size_t, SceneMappingType, std::size_t, std::ptrdiff_t, std::size_t, std::size_t, std::ptrdiff_t, UnsignedShort, SceneFieldFlags) +constructor that omits @ref SceneFieldType as well, but contains an additional +bit offset parameter. + @subsection Trade-SceneFieldData-usage-strings String fields String fields have to be constructed using dedicated constructors that @@ -865,8 +883,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param name Field name * @param mappingType Object mapping type * @param mappingData Object mapping data - * @param fieldType Field type. `SceneFieldType::String*` - * values are not allowed here. + * @param fieldType Field type. @ref SceneFieldType::Bit and + * `SceneFieldType::String*` values are not allowed here. * @param fieldData Field data * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. @@ -887,8 +905,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @brief Construct from 2D type-erased views * @param name Field name * @param mappingData Object mapping data - * @param fieldType Field type. `SceneFieldType::String*` - * values are not allowed here. + * @param fieldType Field type. @ref SceneFieldType::Bit and + * `SceneFieldType::String*` values are not allowed here. * @param fieldData Field data * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. @@ -959,6 +977,126 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { /** @overload */ 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 a bit field with a type-erased mapping view + * @param name Field name + * @param mappingType Object mapping type + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. + * + * Field type is implicitly @ref SceneFieldType::Bit and array size is + * @cpp 0 @ce. Expects that @p mappingData and @p fieldData have the + * same size. At the moment only custom fields can be bits, which means + * this constructor can't be used with a builtin @p name. + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const Containers::StridedBitArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; + #else + /* Has to be a template to avoid having to include StridedBitArrayView + here -- compared to StridedArrayView it's used rather rarely */ + template constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const Containers::BasicStridedBitArrayView<1, T>& fieldData, SceneFieldFlags flags = {}) noexcept; + /* Uhh, and because of the template we need this overload as well */ + template constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, Containers::BasicBitArrayView fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, mappingType, mappingData, Containers::BasicStridedBitArrayView<1, T>{fieldData}, flags} {} + #endif + + /** + * @brief Construct a bit field with a 2D type-erased mapping view + * @param name Field name + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. + * + * Field type is implicitly @ref SceneFieldType::Bit and array size is + * @cpp 0 @ce. Expects that @p mappingData and @p fieldData have the + * same size in the first dimension and that the second dimension of + * @p mappingData is contiguous and its size is either 1, 2, 4 or 8, + * corresponding to one of the @ref SceneMappingType values. At the + * moment only custom fields can be bits, which means this constructor + * can't be used with a builtin @p name. + */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, const Containers::StridedBitArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** + * @brief Construct a bit field + * @param name Field name + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. + * + * Detects @ref SceneMappingType based on @p T and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, const Containers::StridedBitArrayView1D&, SceneFieldFlags). + */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedBitArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedBitArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, stridedArrayView(mappingData), fieldData, flags} {} + + /** + * @brief Construct an array bit field with a type-erased mapping view + * @param name Field name + * @param mappingType Object mapping type + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. + * + * Field type is implicitly @ref SceneFieldType::Bit. Expects that + * @p mappingData and @p fieldData have the same size in the first + * dimension and that the second dimension of @p fieldData is + * contiguous. At the moment only custom fields can be bits, which + * means this constructor can't be used with a builtin @p name. + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const Containers::StridedBitArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept; + #else + /* Has to be a template to avoid having to include StridedBitArrayView + here -- compared to StridedArrayView it's used rather rarely */ + template constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const Containers::BasicStridedBitArrayView<2, T>& fieldData, SceneFieldFlags flags = {}) noexcept; + #endif + + /** + * @brief Construct an array bit field with a 2D type-erased mapping view + * @param name Field name + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. + * + * Field type is implicitly @ref SceneFieldType::Bit and array size is + * @cpp 0 @ce. Expects that @p mappingData and @p fieldData have the + * same size in the first dimension, that the second dimension of + * @p mappingData is contiguous and its size is either 1, 2, 4 or 8, + * corresponding to one of the @ref SceneMappingType values, and that + * the second dimension of @p fieldData is contiguous. At the moment + * only custom fields can be bits, which means this constructor + * can't be used with a builtin @p name. + */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, const Containers::StridedBitArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** + * @brief Construct an array bit field + * @param name Field name + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. + * + * Detects @ref SceneMappingType based on @p T and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, const Containers::StridedBitArrayView2D&, SceneFieldFlags). + */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedBitArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedBitArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, stridedArrayView(mappingData), fieldData, flags} {} + /** * @brief Construct a string field from type-erased views * @param name Field name @@ -1053,8 +1191,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param mappingType Object mapping type * @param mappingOffset Object mapping data offset * @param mappingStride Object mapping data stride - * @param fieldType Field type. `SceneFieldType::String*` - * values are not allowed here. + * @param fieldType Field type. @ref SceneFieldType::Bit and + * `SceneFieldType::String*` values are not allowed here. * @param fieldOffset Field data offset * @param fieldStride Field data stride * @param fieldArraySize Field array size. Use @cpp 0 @ce for @@ -1083,6 +1221,46 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { /** @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 Construct an offset-only bit field + * @param name Field name + * @param size Number of entries + * @param mappingType Object mapping type + * @param mappingOffset Object mapping data offset + * @param mappingStride Object mapping data stride + * @param fieldOffset Field data offset in bytes + * @param fieldBitOffset Field data bit offset + * @param fieldStride Field data stride *in bits* + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is set implicitly. + * @ref SceneFieldFlag::NullTerminatedString is not allowed here. + * + * Instances created this way refer to offsets in unspecified + * external scene data instead of containing the data views directly. + * Useful when the location of the scene data array is not known at + * field construction time. At the moment only custom fields can be + * bits, which means this constructor can't be used with a builtin + * @p name. Expects that @p fieldBitOffset is less than @cpp 8 @ce, for + * consistency with @ref Corrade::Containers::BasicStridedBitArrayView "Containers::StridedBitArrayView" + * also expects that @p size fits into 29 bits on 32-bit platforms and + * into 61 bits on 64-bit platforms. + * + * Note that due to the @cpp constexpr @ce nature of this constructor, + * no @p mappingType checks against @p mappingStride can be done. + * You're encouraged to use the @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, const Containers::StridedBitArrayView1D&, SceneFieldFlags) + * or @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, const Containers::StridedBitArrayView2D&, SceneFieldFlags) + * constructors if you want additional safeguards. + * @see @ref flags(), + * @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, std::size_t fieldOffset, std::size_t fieldBitOffset, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneMappingType mappingType, std::size_t mappingOffset, std::ptrdiff_t mappingStride, std::size_t fieldOffset, std::size_t fieldBitOffset, std::ptrdiff_t fieldStride, SceneFieldFlags flags) noexcept: SceneFieldData{name, size, mappingType, mappingOffset, mappingStride, fieldOffset, fieldBitOffset, fieldStride, 0, flags} {} + /** * @brief Construct an offset-only string field * @param name Field name @@ -1170,22 +1348,53 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { /** * @brief Type-erased field data * - * 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() + * Expects that the field is not @ref SceneFieldType::Bit and does not + * have @ref SceneFieldFlag::OffsetOnly set. For bit fields use + * @ref fieldBitData() instead, for offset-only fields use the + * @ref fieldData(Containers::ArrayView) const overload + * instead. + * @see @ref fieldType(), @ref flags() */ Containers::StridedArrayView1D fieldData() const; /** * @brief Type-erased field data for an offset-only field * - * If the field does not have @ref SceneFieldFlag::OffsetOnly set, the - * @p data parameter is ignored. - * @see @ref flags(), @ref fieldData() const + * Expects that the field is not @ref SceneFieldType::Bit, in that case + * use @ref fieldBitData(Containers::ArrayView) const + * instead. If the field does not have @ref SceneFieldFlag::OffsetOnly + * set, the @p data parameter is ignored. + * @see @ref fieldType(), @ref flags(), @ref fieldData() const */ Containers::StridedArrayView1D fieldData(Containers::ArrayView data) const; + /** + * @brief Bit field data + * + * Expects that the field is @ref SceneFieldType::Bit and does not have + * @ref SceneFieldFlag::OffsetOnly set. For non-bit fields use + * @ref fieldData() instead, for offset-only bit fields use the + * @ref fieldBitData(Containers::ArrayView) const overload + * instead. The returned view is 2D with the second dimension being + * always @cpp 1 @ce for non-array fields. The second dimension is + * always contiguous. + * @see @ref fieldType(), @ref flags() + */ + Containers::StridedBitArrayView2D fieldBitData() const; + + /** + * @brief Bit field data for an offset-only field + * + * Expects that the field is @ref SceneFieldType::Bit, otherwise use + * @ref fieldData(Containers::ArrayView) const instead. If + * the field does not have @ref SceneFieldFlag::OffsetOnly set, the + * @p data parameter is ignored. The returned view is 2D with the + * second dimension being always @cpp 1 @ce for non-array fields. The + * second dimension is always contiguous. + * @see @ref fieldType(), @ref flags(), @ref fieldBitData() const + */ + Containers::StridedBitArrayView2D fieldBitData(Containers::ArrayView data) const; + /** * @brief Base data pointer for a string field * @@ -1222,6 +1431,13 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { private: friend SceneData; + /* Delegated to from all StridedBitArrayView constructors, contains + assertions common for all variants */ + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const void* fieldData, UnsignedByte fieldBitOffset, std::size_t fieldSize, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize, SceneFieldFlags flags) noexcept; + /* Delegated to from all StridedBitArrayView constructors with a 2D + mapping view, contains common SceneMappingType handling */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, const void* fieldData, UnsignedByte fieldBitOffset, std::size_t fieldSize, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize, SceneFieldFlags flags) noexcept; + union Data { /* FFS C++ why this doesn't JUST WORK goddamit?! It's already past the End Of Times AND YET this piece of complex shit can't do the @@ -1256,10 +1472,10 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { plenty, making it relative to _fieldData.pointer accounts for cases where an absolute pointer itself wouldn't fit into 48 bits. - 0 2 4 6 8 LE - +------------+------------+------------+------------+ - | | type | array size | (unused) | - + stride +------------+------------+------------+ + 0 2 4 6 7 8 LE + +------------+------------+------------+------+-----+ + | | type | array size | bOff | | + + stride +------------+------------+------+-----+ | | string data offset | +------------+--------------------------------------+ 8 6 4 2 0 BE */ @@ -1268,14 +1484,15 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { needed. I just want to initialize one or the other union field! */ constexpr explicit Field(): data{} {} - constexpr explicit Field(Short stride, SceneFieldType type, UnsignedShort arraySize): data{stride, type, arraySize} {} + constexpr explicit Field(Short stride, SceneFieldType type, UnsignedShort arraySize, UnsignedByte bitOffset = 0): data{stride, type, arraySize, bitOffset} {} constexpr explicit Field(Short stride, Long offset): strideOffset{(UnsignedLong(stride) & 0xffff)|((UnsignedLong(offset) & 0xffffffffffffull) << 16)} {} struct { Short stride; SceneFieldType type; UnsignedShort arraySize; - /* 2 bytes unused */ + UnsignedByte bitOffset; /* for FieldType::Bit, 5 bits unused */ + /* 1 byte unused */ } data; UnsignedLong strideOffset; /* Upper 48 bits on LE, lower 48 on BE */ } _field; @@ -1448,7 +1665,9 @@ scale, it may prove problematic with huge scenes. Or maybe the internal representation is already optimized for best processing efficiency and the convenience functions would ruin that. The @ref SceneData class thus provides access directly to the stored object mapping and field data using the -@ref mapping() and @ref field() accessors. +@ref mapping() and @ref field() accessors, together with specialized +@ref fieldBits() for accessing bit fields and @ref fieldStrings() for accessing +string fields. However, since each @ref SceneField can be in a variety of types, you're expected to either check that the type is indeed what you expect using @@ -2201,6 +2420,9 @@ class MAGNUM_TRADE_EXPORT SceneData { * get the field in a concrete type. You can also use * @ref field(SceneField) const to directly get data for given named * field. + * + * For @ref SceneFieldType::Bit use @ref fieldBits() or + * @ref fieldBitArrays() instead. * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), * @ref sceneFieldTypeSize(), @ref mutableField(UnsignedInt) */ @@ -2224,6 +2446,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * is expected to correspond to @ref fieldType(UnsignedInt) const. The * field is also expected to not be an array, in that case you need to * use the overload below by using @cpp T[] @ce instead of @cpp T @ce. + * For @ref SceneFieldType::Bit use @ref fieldBits() or + * @ref fieldBitArrays() instead. * * You can also use the non-templated @ref parentsAsArray(), * @ref transformations2DAsArray(), @ref transformations3DAsArray(), @@ -2355,6 +2579,100 @@ class MAGNUM_TRADE_EXPORT SceneData { */ template::value>::type> Containers::StridedArrayView2D::type> mutableField(SceneField name); + /** + * @brief Contents of given bit field + * @m_since_latest + * + * Expects that @p id is smaller than @ref fieldCount() const and that + * the field is @ref SceneFieldType::Bit. The field is also expected to + * not be an array, in that case you need to use + * @ref fieldBitArrays(UnsignedInt) const instead. + * @see @ref fieldType(UnsignedInt) const, + * @ref fieldArraySize(UnsignedInt) const + */ + Containers::StridedBitArrayView1D fieldBits(UnsignedInt id) const; + + /** + * @brief Contents of given array bit field + * @m_since_latest + * + * Same as above, except that it works with array fields as well. The + * second dimension is guaranteed to be contiguous and have the same + * size as reported by @ref fieldArraySize(UnsignedInt) const for given + * field. For non-array fields the second dimension has a size of + * @cpp 1 @ce. + */ + Containers::StridedBitArrayView2D fieldBitArrays(UnsignedInt id) const; + + /** + * @brief Mutable contents of given bit field + * @m_since_latest + * + * Like @ref fieldBits(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::MutableStridedBitArrayView1D mutableFieldBits(UnsignedInt id); + + /** + * @brief Mutable contents of given array bit field + * @m_since_latest + * + * Same as above, except that it works with array fields as well. The + * second dimension is guaranteed to be contiguous and have the same + * size as reported by @ref fieldArraySize(UnsignedInt) const for given + * field. For non-array fields the second dimension has a size of + * @cpp 1 @ce. + */ + Containers::MutableStridedBitArrayView2D mutableFieldBitArrays(UnsignedInt id); + + /** + * @brief Contents of given named bit field + * @m_since_latest + * + * Expects that @p name exists and that the field is + * @ref SceneFieldType::Bit. The field is also expected to not be an + * array, in that case you need to use + * @ref fieldBitArrays(SceneField) const instead. + * @see @ref hasField(), @ref fieldType(SceneField) const, + * @ref fieldArraySize(SceneField) const + */ + Containers::StridedBitArrayView1D fieldBits(SceneField name) const; + + /** + * @brief Contents of given array bit field + * @m_since_latest + * + * Same as above, except that it works with array fields as well. The + * second dimension is guaranteed to be contiguous and have the same + * size as reported by @ref fieldArraySize(SceneField) const for given + * field. For non-array fields the second dimension has a size of + * @cpp 1 @ce. + */ + Containers::StridedBitArrayView2D fieldBitArrays(SceneField name) const; + + /** + * @brief Mutable contents of given bit field + * @m_since_latest + * + * Like @ref fieldBits(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::MutableStridedBitArrayView1D mutableFieldBits(SceneField name); + + /** + * @brief Mutable contents of given array bit field + * @m_since_latest + * + * Same as above, except that it works with array fields as well. The + * second dimension is guaranteed to be contiguous and have the same + * size as reported by @ref fieldArraySize(SceneField) const for given + * field. For non-array fields the second dimension has a size of + * @cpp 1 @ce. + */ + Containers::MutableStridedBitArrayView2D mutableFieldBitArrays(SceneField name); + /** * @brief Base data pointer for given string field * @m_since_latest @@ -3676,7 +3994,9 @@ constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappi (CORRADE_CONSTEXPR_ASSERT(fieldData.stride() >= -32768 && fieldData.stride() <= 32767, "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldData.stride()), Short(fieldData.stride())), (CORRADE_CONSTEXPR_ASSERT(!Implementation::isSceneFieldTypeString(fieldType), - "Trade::SceneFieldData: use a string constructor for" << fieldType), fieldType), + "Trade::SceneFieldData: use a string constructor for" << fieldType), + CORRADE_CONSTEXPR_ASSERT(fieldType != SceneFieldType::Bit, + "Trade::SceneFieldData: use a bit constructor for" << fieldType), fieldType), (CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, _fieldData{fieldData.data()} {} @@ -3694,6 +4014,45 @@ template constexpr SceneFieldData::SceneFieldData(const SceneF flags } {} +/* Assumes that fieldDataBitOffset and fieldDataSize is already bounds-checked, + either by the StridedBitArrayView itself or by the offset-only constructor; + and fieldArraySize as well by the constructor taking the 2D bit view */ +constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const void* const fieldData, const UnsignedByte fieldBitOffset, const std::size_t fieldSize, const std::ptrdiff_t fieldStride, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: + _size{(CORRADE_CONSTEXPR_ASSERT(mappingData.size() == fieldSize, + "Trade::SceneFieldData: expected" << name << "mapping and field view to have the same size but got" << mappingData.size() << "and" << fieldSize), mappingData.size())}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, SceneFieldType::Bit), + "Trade::SceneFieldData:" << SceneFieldType::Bit << "is not a valid type for" << name), name)}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & (SceneFieldFlag::OffsetOnly|SceneFieldFlag::NullTerminatedString)), + "Trade::SceneFieldData: can't pass" << (flags & (SceneFieldFlag::OffsetOnly|SceneFieldFlag::NullTerminatedString)) << "for a view of" << SceneFieldType::Bit), flags)}, + /* Can't use {} because GCC 4.8 then says "warning: parameter 'mappingType' + set but not used" */ + _mappingTypeStringType(UnsignedByte(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()))}, + _mappingData{mappingData.data()}, + _field{ + (CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldStride), Short(fieldStride)), + SceneFieldType::Bit, fieldArraySize, fieldBitOffset}, + _fieldData{fieldData} {} + +#ifndef DOXYGEN_GENERATING_OUTPUT +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const Containers::BasicStridedBitArrayView<1, T>& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingType, mappingData, fieldData.data(), UnsignedByte(fieldData.offset()), fieldData.size(), fieldData.stride(), 0, flags} {} +#endif + +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedBitArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, fieldData, flags} {} + +#ifndef DOXYGEN_GENERATING_OUTPUT +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const Containers::BasicStridedBitArrayView<2, T>& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingType, mappingData, fieldData.data(), UnsignedByte(fieldData.offset()), fieldData.size()[0], + (CORRADE_CONSTEXPR_ASSERT(fieldData.stride()[1] == 1, + "Trade::SceneFieldData: second field view dimension is not contiguous"), fieldData.stride()[0]), + /* There's no need to check that the array size fits into 16 bits, as the + stronger requirement is that the signed stride fits into 16 bits */ + UnsignedShort(fieldData.size()[1]), flags} {} +#endif + +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedBitArrayView2D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, fieldData, flags} {} + template inline SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const char* stringData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, stringData, fieldType, fieldData, 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, const SceneFieldFlags flags) noexcept: @@ -3712,11 +4071,34 @@ constexpr SceneFieldData::SceneFieldData(const SceneField name, const std::size_ (CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldStride), Short(fieldStride)), (CORRADE_CONSTEXPR_ASSERT(!Implementation::isSceneFieldTypeString(fieldType), - "Trade::SceneFieldData: use a string constructor for" << fieldType), fieldType), + "Trade::SceneFieldData: use a string constructor for" << fieldType), + CORRADE_CONSTEXPR_ASSERT(fieldType != SceneFieldType::Bit, + "Trade::SceneFieldData: use a bit constructor for" << fieldType), fieldType), (CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, _fieldData{fieldOffset} {} +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 std::size_t fieldOffset, const std::size_t fieldBitOffset, const std::ptrdiff_t fieldStride, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: + _size{(CORRADE_CONSTEXPR_ASSERT(size < std::size_t{1} << (sizeof(std::size_t)*8 - 3), + "Trade::SceneFieldData: size expected to be smaller than 2^" << Debug::nospace << (sizeof(std::size_t)*8 - 3) << "bits, got" << size), size)}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, SceneFieldType::Bit), + "Trade::SceneFieldData:" << SceneFieldType::Bit << "is not a valid type for" << name), name)}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & SceneFieldFlag::NullTerminatedString), + "Trade::SceneFieldData: can't pass" << (flags & SceneFieldFlag::NullTerminatedString) << "for" << SceneFieldType::Bit), flags|SceneFieldFlag::OffsetOnly)}, + /* Can't use {} because GCC 4.8 then says "warning: parameter 'mappingType' + set but not used" */ + _mappingTypeStringType(UnsignedByte(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))}, + _mappingData{mappingOffset}, + _field{ + (CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldStride), Short(fieldStride)), + SceneFieldType::Bit, fieldArraySize, + (CORRADE_CONSTEXPR_ASSERT(fieldBitOffset < 8, + "Trade::SceneFieldData: bit offset expected to be smaller than 8, got" << fieldBitOffset), UnsignedByte(fieldBitOffset))}, + _fieldData{fieldOffset} {} + 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 std::size_t stringOffset, const SceneFieldType fieldType, const std::size_t fieldOffset, const std::ptrdiff_t fieldStride, const SceneFieldFlags flags) noexcept: _size{size}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index fe709fd29..a10f3fc43 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -23,10 +23,14 @@ DEALINGS IN THE SOFTWARE. */ +/* Including first to verify the StridedBitArrayView include is not needed */ +#include "Magnum/Trade/SceneData.h" + #include #include #include #include +#include #include #include #include @@ -40,7 +44,6 @@ #include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Range.h" -#include "Magnum/Trade/SceneData.h" #ifdef MAGNUM_BUILD_DEPRECATED #include @@ -77,31 +80,42 @@ struct SceneDataTest: TestSuite::Tester { void constructField(); void constructFieldDefault(); void constructFieldCustom(); + void constructFieldBit(); void constructFieldString(); void constructFieldStringNegativeStride(); void constructFieldStringNegativeOffset(); void constructFieldTypeErased(); + void constructFieldTypeErasedBit(); void constructFieldTypeErasedString(); void constructFieldTypeErased2D(); + void constructFieldTypeErased2DBit(); void constructFieldTypeErased2DString(); void constructFieldNonOwningArray(); void constructFieldOffsetOnly(); + void constructFieldOffsetOnlyBit(); void constructFieldOffsetOnlyString(); void constructFieldOffsetOnlyStringNegativeStride(); void constructFieldArray(); + void constructFieldArrayBit(); void constructFieldArrayTypeErased(); + void constructFieldArrayTypeErasedBit(); /* no constructFieldArrayTypeErasedString(), strings can't be arrays */ void constructFieldArrayTypeErased2D(); + void constructFieldArrayTypeErased2DBit(); /* no constructFieldArrayTypeErased2DString(), strings can't be arrays */ void constructFieldArrayOffsetOnly(); + void constructFieldArrayOffsetOnlyBit(); + /* no constructFieldArrayOffsetOnlyString(), strings can't be arrays */ void constructFieldWrongType(); + void constructFieldWrongTypeBit(); void constructFieldWrongTypeString(); void constructFieldInconsistentViewSize(); void constructFieldTooLargeMappingStride(); void constructFieldTooLargeFieldStride(); void constructFieldFlagNotAllowed(); void constructFieldWrongOffsetOnlyDataAccess(); + void constructFieldWrongBitDataAccess(); void constructFieldWrongStringDataAccess(); void constructFieldTypeErased2DWrongSize(); void constructFieldTypeErased2DNonContiguous(); @@ -109,13 +123,17 @@ struct SceneDataTest: TestSuite::Tester { void constructFieldArrayNotAllowed(); void constructFieldArrayTypeErased2DWrongSize(); void constructFieldArrayTypeErased2DNonContiguous(); + void constructFieldBitTooLargeBitOffset(); + void constructFieldBitTooLargeSize(); void construct(); void constructZeroFields(); void constructZeroObjects(); void constructNotOwned(); + void constructBit(); template void constructString(); void constructSpecialStrides(); + void constructSpecialStridesBit(); #ifdef MAGNUM_BUILD_DEPRECATED void constructDeprecated(); void constructDeprecatedBoth2DAnd3D(); @@ -126,6 +144,7 @@ struct SceneDataTest: TestSuite::Tester { void constructInconsistentMappingType(); void constructMappingDataNotContained(); void constructFieldDataNotContained(); + void constructBitFieldDataNotContained(); void constructStringDataNotContained(); void constructMappingTypeTooSmall(); void constructNotOwnedFlagOwned(); @@ -344,6 +363,23 @@ const struct { {"none", 0, 3, 0, false, false, false, false} }; +const struct { + const char* name; + std::ptrdiff_t stride; + std::size_t offset, bitOffset, arrayBitOffset; + UnsignedByte expectedBits; + UnsignedByte expectedArrayBits[2]; +} ConstructSpecialStridesBitData[]{ + {"negative stride", -2*8, 3*2, 3, 5, + 0x0d /* 0b1101 */, + {0x03 /* 0b0011 */, 0x0d /* 0b1101 */}}, + {"zero stride, all ones", 0, 0, 3, 5, + 0x0f, {0x00, 0x0f}}, + /* No change for the array, as it exhibits both values */ + {"zero stride, all zeros", 0, 0, 2, 5, + 0x00, {0x00, 0x0f}}, +}; + #ifdef MAGNUM_BUILD_DEPRECATED const struct { const char* name; @@ -386,29 +422,39 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructField, &SceneDataTest::constructFieldDefault, &SceneDataTest::constructFieldCustom, + &SceneDataTest::constructFieldBit, &SceneDataTest::constructFieldString, &SceneDataTest::constructFieldStringNegativeStride, &SceneDataTest::constructFieldStringNegativeOffset, &SceneDataTest::constructFieldTypeErased, + &SceneDataTest::constructFieldTypeErasedBit, &SceneDataTest::constructFieldTypeErasedString, &SceneDataTest::constructFieldTypeErased2D, + &SceneDataTest::constructFieldTypeErased2DBit, &SceneDataTest::constructFieldTypeErased2DString, &SceneDataTest::constructFieldNonOwningArray, &SceneDataTest::constructFieldOffsetOnly, + &SceneDataTest::constructFieldOffsetOnlyBit, &SceneDataTest::constructFieldOffsetOnlyString, &SceneDataTest::constructFieldOffsetOnlyStringNegativeStride, &SceneDataTest::constructFieldArray, + &SceneDataTest::constructFieldArrayBit, &SceneDataTest::constructFieldArrayTypeErased, + &SceneDataTest::constructFieldArrayTypeErasedBit, &SceneDataTest::constructFieldArrayTypeErased2D, + &SceneDataTest::constructFieldArrayTypeErased2DBit, &SceneDataTest::constructFieldArrayOffsetOnly, + &SceneDataTest::constructFieldArrayOffsetOnlyBit, &SceneDataTest::constructFieldWrongType, + &SceneDataTest::constructFieldWrongTypeBit, &SceneDataTest::constructFieldWrongTypeString, &SceneDataTest::constructFieldInconsistentViewSize, &SceneDataTest::constructFieldTooLargeMappingStride, &SceneDataTest::constructFieldTooLargeFieldStride, &SceneDataTest::constructFieldFlagNotAllowed, &SceneDataTest::constructFieldWrongOffsetOnlyDataAccess, + &SceneDataTest::constructFieldWrongBitDataAccess, &SceneDataTest::constructFieldWrongStringDataAccess, &SceneDataTest::constructFieldTypeErased2DWrongSize, &SceneDataTest::constructFieldTypeErased2DNonContiguous, @@ -416,6 +462,8 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructFieldArrayNotAllowed, &SceneDataTest::constructFieldArrayTypeErased2DWrongSize, &SceneDataTest::constructFieldArrayTypeErased2DNonContiguous, + &SceneDataTest::constructFieldBitTooLargeBitOffset, + &SceneDataTest::constructFieldBitTooLargeSize, &SceneDataTest::construct, &SceneDataTest::constructZeroFields, @@ -424,12 +472,16 @@ SceneDataTest::SceneDataTest() { addInstancedTests({&SceneDataTest::constructNotOwned}, Containers::arraySize(NotOwnedData)); - addTests({&SceneDataTest::constructString, + addTests({&SceneDataTest::constructBit, + &SceneDataTest::constructString, &SceneDataTest::constructString, &SceneDataTest::constructString, &SceneDataTest::constructString, &SceneDataTest::constructSpecialStrides}); + addInstancedTests({&SceneDataTest::constructSpecialStridesBit}, + Containers::arraySize(ConstructSpecialStridesBitData)); + #ifdef MAGNUM_BUILD_DEPRECATED addInstancedTests({&SceneDataTest::constructDeprecated}, Containers::arraySize(ChildrenDeprecatedData)); @@ -442,6 +494,7 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructInconsistentMappingType, &SceneDataTest::constructMappingDataNotContained, &SceneDataTest::constructFieldDataNotContained, + &SceneDataTest::constructBitFieldDataNotContained, &SceneDataTest::constructStringDataNotContained, &SceneDataTest::constructMappingTypeTooSmall, &SceneDataTest::constructNotOwnedFlagOwned, @@ -744,12 +797,16 @@ void SceneDataTest::fieldTypeSizeAlignmentInvalid() { sceneFieldTypeAlignment(SceneFieldType{}); sceneFieldTypeSize(SceneFieldType(0xdead)); sceneFieldTypeAlignment(SceneFieldType(0xdead)); + sceneFieldTypeSize(SceneFieldType::Bit); + sceneFieldTypeAlignment(SceneFieldType::Bit); CORRADE_COMPARE(out.str(), "Trade::sceneFieldTypeSize(): invalid type Trade::SceneFieldType(0x0)\n" "Trade::sceneFieldTypeAlignment(): invalid type Trade::SceneFieldType(0x0)\n" "Trade::sceneFieldTypeSize(): invalid type Trade::SceneFieldType(0xdead)\n" - "Trade::sceneFieldTypeAlignment(): invalid type Trade::SceneFieldType(0xdead)\n"); + "Trade::sceneFieldTypeAlignment(): invalid type Trade::SceneFieldType(0xdead)\n" + "Trade::sceneFieldTypeSize(): can't use with Trade::SceneFieldType::Bit\n" + "Trade::sceneFieldTypeAlignment(): can't use with Trade::SceneFieldType::Bit\n"); } void SceneDataTest::debugFieldType() { @@ -884,6 +941,67 @@ void SceneDataTest::constructFieldCustom() { CORRADE_VERIFY(ranges.fieldData().data() == rangeFieldData); } +constexpr UnsignedShort HiddenMapping[8]{}; +constexpr char HiddenField[5]{}; + +void SceneDataTest::constructFieldBit() { + const UnsignedShort hiddenMappingData[8]{}; + const char hiddenFieldData[5]{}; + + SceneFieldData hidden{sceneFieldCustom(773), Containers::arrayView(hiddenMappingData), Containers::StridedBitArrayView1D{Containers::BitArrayView{hiddenFieldData + 1, 5, 24}, 8, 3}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(hidden.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(hidden.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(hidden.size(), 8); + CORRADE_COMPARE(hidden.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(hidden.mappingData().size(), 8); + CORRADE_COMPARE(hidden.mappingData().stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(hidden.mappingData().data(), hiddenMappingData); + CORRADE_COMPARE(hidden.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(hidden.fieldArraySize(), 0); + CORRADE_COMPARE(hidden.fieldBitData().data(), hiddenFieldData + 1); + CORRADE_COMPARE(hidden.fieldBitData().offset(), 5); + CORRADE_COMPARE(hidden.fieldBitData().size(), (Containers::Size2D{8, 1})); + CORRADE_COMPARE(hidden.fieldBitData().stride(), (Containers::Stride2D{3, 1})); + + /* This is allowed too for simplicity, the parameter has to be large enough + tho */ + char someArray[8*2]; + CORRADE_COMPARE(hidden.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(hidden.mappingData(someArray).size(), 8); + CORRADE_COMPARE(hidden.mappingData(someArray).stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(hidden.mappingData(someArray).data(), hiddenMappingData); + CORRADE_COMPARE(hidden.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(hidden.fieldArraySize(), 0); + CORRADE_COMPARE(hidden.fieldBitData(someArray).data(), hiddenFieldData + 1); + CORRADE_COMPARE(hidden.fieldBitData(someArray).offset(), 5); + CORRADE_COMPARE(hidden.fieldBitData(someArray).size(), (Containers::Size2D{8, 1})); + CORRADE_COMPARE(hidden.fieldBitData(someArray).stride(), (Containers::Stride2D{3, 1})); + + constexpr SceneFieldData chidden{sceneFieldCustom(773), Containers::arrayView(HiddenMapping), Containers::StridedBitArrayView1D{Containers::BitArrayView{HiddenField + 1, 5, 24}, 8, 3}, SceneFieldFlag::ImplicitMapping}; + constexpr SceneField name = chidden.name(); + constexpr SceneFieldFlags flags = chidden.flags(); + constexpr std::size_t size = chidden.size(); + constexpr SceneMappingType mappingType = chidden.mappingType(); + CORRADE_COMPARE(name, sceneFieldCustom(773)); + CORRADE_COMPARE(flags, SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(size, 8); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedShort); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldOffsetOnlyString()) */ + CORRADE_COMPARE(chidden.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(chidden.fieldArraySize(), 0); + /* These are deliberately not constexpr to save header size a bit -- + compared to SceneField APIs they get used very little and it's mostly + useless in a constexpr context anyway */ + CORRADE_COMPARE(chidden.mappingData(someArray).size(), 8); + CORRADE_COMPARE(chidden.mappingData(someArray).stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(chidden.mappingData(someArray).data(), HiddenMapping); + CORRADE_COMPARE(chidden.fieldBitData(someArray).data(), HiddenField + 1); + CORRADE_COMPARE(chidden.fieldBitData(someArray).offset(), 5); + CORRADE_COMPARE(chidden.fieldBitData(someArray).size(), (Containers::Size2D{8, 1})); + CORRADE_COMPARE(chidden.fieldBitData(someArray).stride(), (Containers::Stride2D{3, 1})); +} + void SceneDataTest::constructFieldString() { const UnsignedLong nameMappingData[3]{}; @@ -1035,6 +1153,50 @@ void SceneDataTest::constructFieldTypeErased() { CORRADE_COMPARE(cscalings.fieldData().data(), ScalingFieldData); } +void SceneDataTest::constructFieldTypeErasedBit() { + const UnsignedShort hiddenMappingData[8]{}; + const char hiddenFieldData[5]{}; + + SceneFieldData hidden{sceneFieldCustom(773), SceneMappingType::UnsignedShort, Containers::arrayCast(Containers::stridedArrayView(hiddenMappingData)), Containers::StridedBitArrayView1D{Containers::BitArrayView{hiddenFieldData + 1, 5, 24}, 8, 3}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(hidden.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(hidden.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(hidden.size(), 8); + CORRADE_COMPARE(hidden.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(hidden.mappingData().size(), 8); + CORRADE_COMPARE(hidden.mappingData().stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(hidden.mappingData().data(), hiddenMappingData); + CORRADE_COMPARE(hidden.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(hidden.fieldArraySize(), 0); + CORRADE_COMPARE(hidden.fieldBitData().data(), hiddenFieldData + 1); + CORRADE_COMPARE(hidden.fieldBitData().offset(), 5); + CORRADE_COMPARE(hidden.fieldBitData().size(), (Containers::Size2D{8, 1})); + CORRADE_COMPARE(hidden.fieldBitData().stride(), (Containers::Stride2D{3, 1})); + + constexpr SceneFieldData chidden{sceneFieldCustom(773), SceneMappingType::UnsignedShort, Containers::StridedArrayView1D{HiddenMapping, 8, sizeof(UnsignedShort)}, Containers::StridedBitArrayView1D{Containers::BitArrayView{HiddenField + 1, 5, 24}, 8, 3}, SceneFieldFlag::ImplicitMapping}; + constexpr SceneField name = chidden.name(); + constexpr SceneFieldFlags flags = chidden.flags(); + constexpr std::size_t size = chidden.size(); + constexpr SceneMappingType mappingType = chidden.mappingType(); + CORRADE_COMPARE(name, sceneFieldCustom(773)); + CORRADE_COMPARE(flags, SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(size, 8); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedShort); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldOffsetOnlyString()) */ + CORRADE_COMPARE(chidden.mappingData().size(), 8); + CORRADE_COMPARE(chidden.mappingData().stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(chidden.mappingData().data(), HiddenMapping); + /* These are deliberately not constexpr to save header size a bit -- + compared to SceneField APIs they get used very little and it's mostly + useless in a constexpr context anyway */ + CORRADE_COMPARE(chidden.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(chidden.fieldArraySize(), 0); + CORRADE_COMPARE(chidden.fieldBitData().data(), HiddenField + 1); + CORRADE_COMPARE(chidden.fieldBitData().offset(), 5); + CORRADE_COMPARE(chidden.fieldBitData().size(), (Containers::Size2D{8, 1})); + CORRADE_COMPARE(chidden.fieldBitData().stride(), (Containers::Stride2D{3, 1})); +} + void SceneDataTest::constructFieldTypeErasedString() { const UnsignedLong nameMappingData[3]{}; const char nameStringData[15]{}; @@ -1083,6 +1245,32 @@ void SceneDataTest::constructFieldTypeErased2D() { a corresponding SceneMappingType */ } +void SceneDataTest::constructFieldTypeErased2DBit() { + char hiddenMappingData[16*sizeof(UnsignedShort)]{}; + char hiddenFieldData[5]{}; + auto hiddenMappingView = Containers::StridedArrayView2D{hiddenMappingData, {16, sizeof(UnsignedShort)}}.every(2); + /* No type-erased 2D view for the bit field data -- the 2D view constructor + is the array constructor which is tested in constructFieldArrayBit() */ + + SceneFieldData hidden{sceneFieldCustom(773), hiddenMappingView, Containers::StridedBitArrayView1D{Containers::BitArrayView{hiddenFieldData + 1, 5, 24}, 8, 3}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(hidden.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(hidden.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(hidden.size(), 8); + CORRADE_COMPARE(hidden.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(hidden.mappingData().size(), 8); + CORRADE_COMPARE(hidden.mappingData().stride(), 2*sizeof(UnsignedShort)); + CORRADE_COMPARE(hidden.mappingData().data(), hiddenMappingData); + CORRADE_COMPARE(hidden.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(hidden.fieldArraySize(), 0); + CORRADE_COMPARE(hidden.fieldBitData().data(), hiddenFieldData + 1); + CORRADE_COMPARE(hidden.fieldBitData().offset(), 5); + CORRADE_COMPARE(hidden.fieldBitData().size(), (Containers::Size2D{8, 1})); + CORRADE_COMPARE(hidden.fieldBitData().stride(), (Containers::Stride2D{3, 1})); + + /* 2D type-erased construction is not constexpr due to branching to select + a corresponding SceneMappingType */ +} + void SceneDataTest::constructFieldTypeErased2DString() { char nameMappingData[6*sizeof(UnsignedLong)]{}; char nameFieldData[6*sizeof(Containers::Pair)]{}; @@ -1172,6 +1360,66 @@ void SceneDataTest::constructFieldOffsetOnly() { TestSuite::Compare::Container); } +void SceneDataTest::constructFieldOffsetOnlyBit() { + struct Data { + Byte parent; + UnsignedInt object; + char yesInFifthBit; + } data[]{ + {0, 2, '\x10'}, + {0, 15, '\xef'}, + {0, 22, '\x10'} + }; + + SceneFieldData a{sceneFieldCustom(773), 3, SceneMappingType::UnsignedInt, offsetof(Data, object), sizeof(Data), offsetof(Data, yesInFifthBit), 4, sizeof(Data)*8, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(a.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(a.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(a.size(), 3); + CORRADE_COMPARE(a.mappingType(), SceneMappingType::UnsignedInt); + CORRADE_COMPARE(a.mappingData(data).size(), 3); + CORRADE_COMPARE(a.mappingData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.mappingData(data)), + Containers::arrayView({2, 15, 22}), + TestSuite::Compare::Container); + CORRADE_COMPARE(a.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(a.fieldArraySize(), 0); + CORRADE_COMPARE(a.fieldBitData(data).data(), &data[0].yesInFifthBit); + CORRADE_COMPARE(a.fieldBitData(data).offset(), 4); + CORRADE_COMPARE(a.fieldBitData(data).size(), (Containers::Size2D{3, 1})); + CORRADE_COMPARE(a.fieldBitData(data).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((a.fieldBitData(data).transposed<0, 1>())[0], + Containers::stridedArrayView({true, false, true}).sliceBit(0), + TestSuite::Compare::Container); + + constexpr SceneFieldData ca{sceneFieldCustom(773), 3, SceneMappingType::UnsignedInt, offsetof(Data, object), sizeof(Data), offsetof(Data, yesInFifthBit), 4, sizeof(Data)*8, SceneFieldFlag::OrderedMapping}; + constexpr SceneField name = ca.name(); + constexpr SceneFieldFlags flags = ca.flags(); + constexpr std::size_t size = ca.size(); + constexpr SceneMappingType mappingType = ca.mappingType(); + CORRADE_COMPARE(name, sceneFieldCustom(773)); + CORRADE_COMPARE(flags, SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(size, 3); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedInt); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldOffsetOnlyString()) */ + CORRADE_COMPARE(ca.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(ca.fieldArraySize(), 0); + /* These are deliberately not constexpr to save header size a bit -- + compared to SceneField APIs they get used very little and it's mostly + useless in a constexpr context anyway */ + CORRADE_COMPARE(ca.mappingData(data).size(), 3); + CORRADE_COMPARE(ca.mappingData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(ca.mappingData(data)), + Containers::arrayView({2, 15, 22}), + TestSuite::Compare::Container); + CORRADE_COMPARE(ca.fieldBitData(data).offset(), 4); + CORRADE_COMPARE(ca.fieldBitData(data).size(), (Containers::Size2D{3, 1})); + CORRADE_COMPARE(ca.fieldBitData(data).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((ca.fieldBitData(data).transposed<0, 1>())[0], + Containers::stridedArrayView({true, false, true}).sliceBit(0), + TestSuite::Compare::Container); +} + void SceneDataTest::constructFieldOffsetOnlyString() { const char string[] = "NAMES:eyehandnoseleg"; struct Data { @@ -1306,6 +1554,53 @@ void SceneDataTest::constructFieldArray() { CORRADE_COMPARE(cdata.fieldData().data(), ArrayOffsetFieldData); } +constexpr UnsignedLong ArrayHiddenMapping[3]{}; +constexpr char ArrayHiddenField[5]{}; + +void SceneDataTest::constructFieldArrayBit() { + const UnsignedLong hiddenMappingData[3]{}; + const char hiddenFieldData[5]{}; + + SceneFieldData data{sceneFieldCustom(773), Containers::arrayView(hiddenMappingData), Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData + 1, 5, 24}, {3, 4}, {8, 1}}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(data.mappingData().size(), 3); + CORRADE_COMPARE(data.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(data.mappingData().data(), hiddenMappingData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldBitData().data(), hiddenFieldData + 1); + CORRADE_COMPARE(data.fieldBitData().offset(), 5); + CORRADE_COMPARE(data.fieldBitData().size(), (Containers::Size2D{3, 4})); + CORRADE_COMPARE(data.fieldBitData().stride(), (Containers::Stride2D{8, 1})); + + constexpr SceneFieldData cdata{sceneFieldCustom(773), Containers::arrayView(ArrayHiddenMapping), Containers::StridedBitArrayView2D{Containers::BitArrayView{ArrayHiddenField + 1, 5, 24}, {3, 4}, {8, 1}}, SceneFieldFlag::OrderedMapping}; + constexpr SceneField name = cdata.name(); + constexpr SceneFieldFlags flags = cdata.flags(); + constexpr std::size_t size = cdata.size(); + constexpr SceneMappingType mappingType = cdata.mappingType(); + CORRADE_COMPARE(name, sceneFieldCustom(773)); + CORRADE_COMPARE(flags, SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(size, 3); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedLong); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldOffsetOnlyString()) */ + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(cdata.fieldArraySize(), 4); + /* These are deliberately not constexpr to save header size a bit -- + compared to SceneField APIs they get used very little and it's mostly + useless in a constexpr context anyway */ + CORRADE_COMPARE(cdata.mappingData().size(), 3); + CORRADE_COMPARE(cdata.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(cdata.mappingData().data(), ArrayHiddenMapping); + CORRADE_COMPARE(cdata.fieldBitData().data(), ArrayHiddenField + 1); + CORRADE_COMPARE(cdata.fieldBitData().offset(), 5); + CORRADE_COMPARE(cdata.fieldBitData().size(), (Containers::Size2D{3, 4})); + CORRADE_COMPARE(cdata.fieldBitData().stride(), (Containers::Stride2D{8, 1})); +} + void SceneDataTest::constructFieldArrayTypeErased() { UnsignedByte offsetMappingData[3]; Int offsetFieldData[3*4]; @@ -1348,6 +1643,50 @@ void SceneDataTest::constructFieldArrayTypeErased() { CORRADE_COMPARE(cdata.fieldData().data(), ArrayOffsetFieldData); } +void SceneDataTest::constructFieldArrayTypeErasedBit() { + const UnsignedLong hiddenMappingData[3]{}; + const char hiddenFieldData[5]{}; + + SceneFieldData data{sceneFieldCustom(773), SceneMappingType::UnsignedLong, Containers::arrayCast(Containers::stridedArrayView(hiddenMappingData)), Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData + 1, 5, 24}, {3, 4}, {8, 1}}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(data.mappingData().size(), 3); + CORRADE_COMPARE(data.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(data.mappingData().data(), hiddenMappingData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldBitData().data(), hiddenFieldData + 1); + CORRADE_COMPARE(data.fieldBitData().offset(), 5); + CORRADE_COMPARE(data.fieldBitData().size(), (Containers::Size2D{3, 4})); + CORRADE_COMPARE(data.fieldBitData().stride(), (Containers::Stride2D{8, 1})); + + constexpr SceneFieldData cdata{sceneFieldCustom(773), SceneMappingType::UnsignedLong, Containers::StridedArrayView1D{ArrayHiddenMapping}, Containers::StridedBitArrayView2D{Containers::BitArrayView{ArrayHiddenField + 1, 5, 24}, {3, 4}, {8, 1}}, SceneFieldFlag::OrderedMapping}; + constexpr SceneField name = cdata.name(); + constexpr SceneFieldFlags flags = cdata.flags(); + constexpr std::size_t size = cdata.size(); + constexpr SceneMappingType mappingType = cdata.mappingType(); + CORRADE_COMPARE(name, sceneFieldCustom(773)); + CORRADE_COMPARE(flags, SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(size, 3); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedLong); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldOffsetOnlyString()) */ + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(cdata.fieldArraySize(), 4); + /* These are deliberately not constexpr to save header size a bit -- + compared to SceneField APIs they get used very little and it's mostly + useless in a constexpr context anyway */ + CORRADE_COMPARE(cdata.mappingData().size(), 3); + CORRADE_COMPARE(cdata.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(cdata.mappingData().data(), ArrayHiddenMapping); + CORRADE_COMPARE(cdata.fieldBitData().data(), ArrayHiddenField + 1); + CORRADE_COMPARE(cdata.fieldBitData().offset(), 5); + CORRADE_COMPARE(cdata.fieldBitData().size(), (Containers::Size2D{3, 4})); + CORRADE_COMPARE(cdata.fieldBitData().stride(), (Containers::Stride2D{8, 1})); +} + void SceneDataTest::constructFieldArrayTypeErased2D() { char offsetMappingData[3*sizeof(UnsignedByte)]; char offsetFieldData[3*4*sizeof(Int)]; @@ -1369,6 +1708,29 @@ void SceneDataTest::constructFieldArrayTypeErased2D() { a corresponding SceneMappingType */ } +void SceneDataTest::constructFieldArrayTypeErased2DBit() { + char hiddenMappingData[3*sizeof(UnsignedLong)]{}; + char hiddenFieldData[5]{}; + + SceneFieldData data{sceneFieldCustom(773), Containers::StridedArrayView2D{hiddenMappingData, {3, sizeof(UnsignedLong)}}, Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData + 1, 5, 24}, {3, 4}, {8, 1}}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(data.mappingData().size(), 3); + CORRADE_COMPARE(data.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(data.mappingData().data(), hiddenMappingData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldBitData().data(), hiddenFieldData + 1); + CORRADE_COMPARE(data.fieldBitData().offset(), 5); + CORRADE_COMPARE(data.fieldBitData().size(), (Containers::Size2D{3, 4})); + CORRADE_COMPARE(data.fieldBitData().stride(), (Containers::Stride2D{8, 1})); + + /* 2D type-erased construction is not constexpr due to branching to select + a corresponding SceneMappingType */ +} + void SceneDataTest::constructFieldArrayOffsetOnly() { struct Data { Byte parent; @@ -1432,20 +1794,88 @@ void SceneDataTest::constructFieldArrayOffsetOnly() { TestSuite::Compare::Container); } +void SceneDataTest::constructFieldArrayOffsetOnlyBit() { + struct Data { + Byte parent; + UnsignedInt object; + char yesNoInUpperBits; + } data[]{ + {0, 2, '\x80'}, + {0, 15, '\x7f'}, + {0, 22, '\x80'} + }; + + SceneFieldData a{sceneFieldCustom(773), 3, SceneMappingType::UnsignedInt, offsetof(Data, object), sizeof(Data), offsetof(Data, yesNoInUpperBits), 6, sizeof(Data)*8, 2, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(a.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(a.name(), sceneFieldCustom(773)); + CORRADE_COMPARE(a.size(), 3); + CORRADE_COMPARE(a.mappingType(), SceneMappingType::UnsignedInt); + CORRADE_COMPARE(a.mappingData(data).size(), 3); + CORRADE_COMPARE(a.mappingData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.mappingData(data)), + Containers::arrayView({2, 15, 22}), + TestSuite::Compare::Container); + CORRADE_COMPARE(a.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(a.fieldArraySize(), 2); + CORRADE_COMPARE(a.fieldBitData(data).offset(), 6); + CORRADE_COMPARE(a.fieldBitData(data).size(), (Containers::Size2D{3, 2})); + CORRADE_COMPARE(a.fieldBitData(data).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((a.fieldBitData(data).transposed<0, 1>())[0], + Containers::stridedArrayView({false, true, false}).sliceBit(0), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((a.fieldBitData(data).transposed<0, 1>())[1], + Containers::stridedArrayView({true, false, true}).sliceBit(0), + TestSuite::Compare::Container); + + constexpr SceneFieldData ca{sceneFieldCustom(773), 3, SceneMappingType::UnsignedInt, offsetof(Data, object), sizeof(Data), offsetof(Data, yesNoInUpperBits), 6, sizeof(Data)*8, 2, SceneFieldFlag::OrderedMapping}; + constexpr SceneField name = ca.name(); + constexpr SceneFieldFlags flags = ca.flags(); + constexpr std::size_t size = ca.size(); + constexpr SceneMappingType mappingType = ca.mappingType(); + CORRADE_COMPARE(name, sceneFieldCustom(773)); + CORRADE_COMPARE(flags, SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(size, 3); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedInt); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldOffsetOnlyString()) */ + CORRADE_COMPARE(ca.fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(ca.fieldArraySize(), 2); + /* These are deliberately not constexpr to save header size a bit -- + compared to SceneField APIs they get used very little and it's mostly + useless in a constexpr context anyway */ + CORRADE_COMPARE(ca.mappingData(data).size(), 3); + CORRADE_COMPARE(ca.mappingData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(ca.mappingData(data)), + Containers::arrayView({2, 15, 22}), + TestSuite::Compare::Container); + CORRADE_COMPARE(ca.fieldBitData(data).offset(), 6); + CORRADE_COMPARE(ca.fieldBitData(data).size(), (Containers::Size2D{3, 2})); + CORRADE_COMPARE(ca.fieldBitData(data).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((ca.fieldBitData(data).transposed<0, 1>())[0], + Containers::stridedArrayView({false, true, false}).sliceBit(0), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((ca.fieldBitData(data).transposed<0, 1>())[1], + Containers::stridedArrayView({true, false, true}).sliceBit(0), + TestSuite::Compare::Container); +} + void SceneDataTest::constructFieldInconsistentViewSize() { CORRADE_SKIP_IF_NO_ASSERT(); const UnsignedShort mappingData[3]{}; const Complexd rotationFieldData[2]; + const char hiddenFieldData[1]{}; const char helloStringData[5]{}; const UnsignedLong helloOffsetsData[2]{}; std::ostringstream out; Error redirectError{&out}; SceneFieldData{SceneField::Rotation, Containers::arrayView(mappingData), Containers::arrayView(rotationFieldData)}; + SceneFieldData{sceneFieldCustom(773), Containers::arrayView(mappingData), Containers::BitArrayView{hiddenFieldData, 0, 2}}; SceneFieldData{sceneFieldCustom(32), Containers::arrayView(mappingData), helloStringData, SceneFieldType::StringOffset64, Containers::arrayView(helloOffsetsData)}; CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: expected Trade::SceneField::Rotation mapping and field view to have the same size but got 3 and 2\n" + "Trade::SceneFieldData: expected Trade::SceneField::Custom(773) mapping and field view to have the same size but got 3 and 2\n" "Trade::SceneFieldData: expected Trade::SceneField::Custom(32) mapping and field view to have the same size but got 3 and 2\n"); } @@ -1459,13 +1889,33 @@ void SceneDataTest::constructFieldWrongType() { Error redirectError{&out}; SceneFieldData{SceneField::Transformation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; SceneFieldData{SceneField::Transformation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion)}; - /** @todo test also builtin string fields with non-string types once there - are any */ + /** @todo test also builtin bit and string fields with non-string types + once there are any */ CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: Trade::SceneFieldType::Quaternion is not a valid type for Trade::SceneField::Transformation\n" "Trade::SceneFieldData: Trade::SceneFieldType::Quaternion is not a valid type for Trade::SceneField::Transformation\n"); } +void SceneDataTest::constructFieldWrongTypeBit() { + CORRADE_SKIP_IF_NO_ASSERT(); + + const UnsignedShort hiddenMappingData[3]{}; + const bool hiddenFieldData[3]{}; + + std::ostringstream out; + Error redirectError{&out}; + /* Non-bit constructors with SceneFieldType::Bit. Only type-erased, 2D and + offset-only construction, the regular and array constructor from typed + views have SceneFieldType implicit. */ + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedShort, Containers::arrayView(hiddenMappingData), SceneFieldType::Bit, Containers::arrayView(hiddenFieldData)}; + SceneFieldData{sceneFieldCustom(773), Containers::arrayCast<2, const char>(Containers::arrayView(hiddenMappingData)), SceneFieldType::Bit, Containers::arrayCast<2, const char>(Containers::arrayView(hiddenFieldData))}; + SceneFieldData{sceneFieldCustom(773), 3, SceneMappingType::UnsignedShort, 0, 2, SceneFieldType::Bit, 0, 1}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: use a bit constructor for Trade::SceneFieldType::Bit\n" + "Trade::SceneFieldData: use a bit constructor for Trade::SceneFieldType::Bit\n" + "Trade::SceneFieldData: use a bit constructor for Trade::SceneFieldType::Bit\n"); +} + void SceneDataTest::constructFieldWrongTypeString() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -1512,6 +1962,11 @@ void SceneDataTest::constructFieldTooLargeMappingStride() { SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 32767, SceneFieldType::UnsignedInt, 0, 4}; SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 65536, -32768, SceneFieldType::UnsignedInt, 0, 4}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}, Containers::BitArrayView{enough, 0, 2}}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>(), Containers::BitArrayView{enough, 0, 2}}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 0, 32767, 0, 0, 4}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 65536, -32768, 0, 0, 4}; + SceneFieldData{sceneFieldCustom(25), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}, helloStringData, SceneFieldType::StringOffset32, enough}; SceneFieldData{sceneFieldCustom(25), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>(), helloStringData, SceneFieldType::StringOffset32, enough}; SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 0, 32767, 0, SceneFieldType::StringOffset32, 0, 4}; @@ -1524,6 +1979,11 @@ void SceneDataTest::constructFieldTooLargeMappingStride() { SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 32768, SceneFieldType::UnsignedInt, 0, 4}; SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 65538, -32769, SceneFieldType::UnsignedInt, 0, 4}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}, Containers::BitArrayView{enough, 0, 2}}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>(), Containers::BitArrayView{enough, 0, 2}}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 0, 32768, 0, 0, 4}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 65538, -32769, 0, 0, 4}; + SceneFieldData{sceneFieldCustom(25), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}, helloStringData, SceneFieldType::StringOffset32, enough}; SceneFieldData{sceneFieldCustom(25), SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>(), helloStringData, SceneFieldType::StringOffset32, enough}; SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 0, 32768, 0, SceneFieldType::StringOffset32, 0, 4}; @@ -1534,6 +1994,11 @@ void SceneDataTest::constructFieldTooLargeMappingStride() { "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got 32768\n" "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got -32769\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got 32768\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got -32769\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got 32768\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got -32769\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got 32768\n" "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got -32769\n" "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits but got 32768\n" @@ -1553,6 +2018,11 @@ void SceneDataTest::constructFieldTooLargeFieldStride() { SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32767}; SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65536, -32768}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, enough, Containers::StridedBitArrayView1D{Containers::BitArrayView{toomuch}, 2, 32767}}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, enough, Containers::StridedBitArrayView1D{Containers::BitArrayView{toomuch}, 2, 32768}.flipped<0>()}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32767}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65536, -32768}; + SceneFieldData{sceneFieldCustom(35), SceneMappingType::UnsignedInt, enough, helloStringData, SceneFieldType::StringRangeNullTerminated32, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}}; SceneFieldData{sceneFieldCustom(35), SceneMappingType::UnsignedInt, enough, helloStringData, SceneFieldType::StringRangeNullTerminated32, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>()}; SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 0, 4, 0, SceneFieldType::StringRangeNullTerminated32, 0, 32767}; @@ -1565,6 +2035,11 @@ void SceneDataTest::constructFieldTooLargeFieldStride() { SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32768}; SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65538, -32769}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, enough, Containers::StridedBitArrayView1D{Containers::BitArrayView{toomuch}, 2, 32768}}; + SceneFieldData{sceneFieldCustom(773), SceneMappingType::UnsignedInt, enough, Containers::StridedBitArrayView1D{Containers::BitArrayView{toomuch}, 2, 32769}.flipped<0>()}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32768}; + SceneFieldData{sceneFieldCustom(773), 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65538, -32769}; + SceneFieldData{sceneFieldCustom(35), SceneMappingType::UnsignedInt, enough, helloStringData, SceneFieldType::StringRangeNullTerminated32, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}}; SceneFieldData{sceneFieldCustom(35), SceneMappingType::UnsignedInt, enough, helloStringData, SceneFieldType::StringRangeNullTerminated32, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>()}; SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 0, 4, 0, SceneFieldType::StringRangeNullTerminated32, 0, 32768}; @@ -1575,6 +2050,11 @@ void SceneDataTest::constructFieldTooLargeFieldStride() { "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got 32768\n" "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got -32769\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got 32768\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got -32769\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got 32768\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got -32769\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got 32768\n" "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got -32769\n" "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got 32768\n" @@ -1586,11 +2066,13 @@ void SceneDataTest::constructFieldFlagNotAllowed() { const UnsignedShort mappingData[3]{}; const Quaternion rotationFieldData[3]; + const char hiddenFieldData[1]{}; const char helloStringData[5]{}; const UnsignedShort helloFieldData[3]{}; /* These are fine */ SceneFieldData{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion), SceneFieldFlag::OffsetOnly}; + SceneFieldData{sceneFieldCustom(773), 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), 0, 0, sizeof(Quaternion), SceneFieldFlag::OffsetOnly}; SceneFieldData{sceneFieldCustom(24), Containers::arrayView(mappingData), helloStringData, SceneFieldType::StringOffset32, helloFieldData, SceneFieldFlag::NullTerminatedString}; SceneFieldData{sceneFieldCustom(24), 3, SceneMappingType::UnsignedShort, 0, 2, 0, SceneFieldType::StringOffset32, 0, 4, SceneFieldFlag::NullTerminatedString}; @@ -1600,12 +2082,24 @@ void SceneDataTest::constructFieldFlagNotAllowed() { SceneFieldData{SceneField::Rotation, Containers::arrayView(mappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::NullTerminatedString}; SceneFieldData{SceneField::Rotation, Containers::arrayView(mappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OffsetOnly|SceneFieldFlag::NullTerminatedString}; SceneFieldData{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, 2, SceneFieldType::Quaternion, 0, 16, SceneFieldFlag::NullTerminatedString}; + + SceneFieldData{sceneFieldCustom(773), Containers::arrayView(mappingData), Containers::BitArrayView{hiddenFieldData, 0, 3}, SceneFieldFlag::OffsetOnly}; + SceneFieldData{sceneFieldCustom(773), Containers::arrayView(mappingData), Containers::BitArrayView{hiddenFieldData, 0, 3}, SceneFieldFlag::NullTerminatedString}; + SceneFieldData{sceneFieldCustom(773), Containers::arrayView(mappingData), Containers::BitArrayView{hiddenFieldData, 0, 3}, SceneFieldFlag::OffsetOnly|SceneFieldFlag::NullTerminatedString}; + SceneFieldData{sceneFieldCustom(773), 3, SceneMappingType::UnsignedShort, 0, 2, 0, 0, 16, SceneFieldFlag::NullTerminatedString}; + SceneFieldData{sceneFieldCustom(24), Containers::arrayView(mappingData), helloStringData, SceneFieldType::StringOffset32, helloFieldData, SceneFieldFlag::OffsetOnly}; CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view of Trade::SceneFieldType::Quaternion\n" "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::NullTerminatedString for a view of Trade::SceneFieldType::Quaternion\n" "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly|Trade::SceneFieldFlag::NullTerminatedString for a view of Trade::SceneFieldType::Quaternion\n" "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::NullTerminatedString for Trade::SceneFieldType::Quaternion\n" + + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view of Trade::SceneFieldType::Bit\n" + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::NullTerminatedString for a view of Trade::SceneFieldType::Bit\n" + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly|Trade::SceneFieldFlag::NullTerminatedString for a view of Trade::SceneFieldType::Bit\n" + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::NullTerminatedString for Trade::SceneFieldType::Bit\n" + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view\n"); } @@ -1618,26 +2112,54 @@ void SceneDataTest::constructFieldWrongOffsetOnlyDataAccess() { 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)}; - SceneFieldData c{sceneFieldCustom(25), 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), 0, SceneFieldType::StringRange32, 0, 64}; + SceneFieldData c{sceneFieldCustom(773), 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), 0, 0, 1}; + SceneFieldData d{sceneFieldCustom(25), 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), 0, SceneFieldType::StringRange32, 0, 64}; CORRADE_COMPARE(a.flags(), SceneFieldFlags{}); CORRADE_COMPARE(b.flags(), SceneFieldFlag::OffsetOnly); CORRADE_COMPARE(c.flags(), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(d.flags(), SceneFieldFlag::OffsetOnly); a.mappingData(rotationMappingData); /* This is fine, no asserts */ - a.fieldData(rotationFieldData); - c.stringData(hello); + b.fieldData(rotationFieldData); + c.fieldBitData(hello); + d.stringData(hello); std::ostringstream out; Error redirectError{&out}; b.mappingData(); b.fieldData(); - c.stringData(); + d.fieldBitData(); + d.stringData(); CORRADE_COMPARE(out.str(), "Trade::SceneFieldData::mappingData(): the field is offset-only, supply a data array\n" "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array\n" + "Trade::SceneFieldData::fieldBitData(): the field is offset-only, supply a data array\n" "Trade::SceneFieldData::stringData(): the field is offset-only, supply a data array\n"); } +void SceneDataTest::constructFieldWrongBitDataAccess() { + CORRADE_SKIP_IF_NO_ASSERT(); + + const UnsignedShort mappingData[3]{}; + const char hiddenFieldData[3]{}; + const Quaternion rotationFieldData[3]; + + SceneFieldData a{sceneFieldCustom(773), Containers::arrayView(mappingData), Containers::BitArrayView{hiddenFieldData, 0, 3}}; + SceneFieldData b{SceneField::Rotation, Containers::arrayView(mappingData), Containers::arrayView(rotationFieldData)}; + + std::ostringstream out; + Error redirectError{&out}; + a.fieldData(); + a.fieldData(hiddenFieldData); + b.fieldBitData(); + b.fieldBitData(rotationFieldData); + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData::fieldData(): the field is Trade::SceneFieldType::Bit, use fieldBitData() instead\n" + "Trade::SceneFieldData::fieldData(): the field is Trade::SceneFieldType::Bit, use fieldBitData() instead\n" + "Trade::SceneFieldData::fieldBitData(): the field is Trade::SceneFieldType::Quaternion, not a bit\n" + "Trade::SceneFieldData::fieldBitData(): the field is Trade::SceneFieldType::Quaternion, not a bit\n"); +} + void SceneDataTest::constructFieldWrongStringDataAccess() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -1660,6 +2182,7 @@ void SceneDataTest::constructFieldTypeErased2DWrongSize() { char mappingData[5*sizeof(UnsignedInt)]; char rotationFieldData[4*sizeof(Complex)]; + char hiddenFieldData[1]{}; char helloStringData[3]{}; char helloFieldData[4*sizeof(UnsignedShort)]{}; @@ -1669,6 +2192,12 @@ void SceneDataTest::constructFieldTypeErased2DWrongSize() { Containers::StridedArrayView2D{mappingData, {4, 5}}.every(2), SceneFieldType::Complex, Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + SceneFieldData{sceneFieldCustom(773), + Containers::StridedArrayView2D{mappingData, {4, 5}}.every(2), + Containers::StridedBitArrayView1D{Containers::BitArrayView{hiddenFieldData}, 4}.every(2)}; + SceneFieldData{sceneFieldCustom(773), + Containers::StridedArrayView2D{mappingData, {4, 5}}.every(2), + Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData}, {4, 2}}.every(2)}; SceneFieldData{sceneFieldCustom(32), Containers::StridedArrayView2D{mappingData, {4, 5}}.every(2), helloStringData, @@ -1678,12 +2207,15 @@ void SceneDataTest::constructFieldTypeErased2DWrongSize() { Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedInt)}}.every(2), SceneFieldType::Vector3, Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + /* All second field dimension sizes are fine for SceneFieldType::Bit */ SceneFieldData{sceneFieldCustom(32), Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedInt)}}.every(2), helloStringData, SceneFieldType::StringRange16, Containers::StridedArrayView2D{helloFieldData, {4, sizeof(UnsignedShort)}}.every(2)}; CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got 5\n" + "Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got 5\n" "Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got 5\n" "Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got 5\n" "Trade::SceneFieldData: second field view dimension size 8 doesn't match Trade::SceneFieldType::Vector3\n" @@ -1695,6 +2227,7 @@ void SceneDataTest::constructFieldTypeErased2DNonContiguous() { char mappingData[8*sizeof(UnsignedInt)]; char rotationFieldData[8*sizeof(Complex)]; + char hiddenFieldData[1]{}; char helloStringData[3]{}; char helloFieldData[8*sizeof(UnsignedShort)]{}; @@ -1704,6 +2237,9 @@ void SceneDataTest::constructFieldTypeErased2DNonContiguous() { Containers::StridedArrayView2D{mappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), SceneFieldType::Complex, Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}}; + SceneFieldData{sceneFieldCustom(773), + Containers::StridedArrayView2D{mappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), + Containers::StridedBitArrayView1D{Containers::BitArrayView{hiddenFieldData}, 4}}; SceneFieldData{sceneFieldCustom(32), Containers::StridedArrayView2D{mappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), helloStringData, @@ -1713,6 +2249,9 @@ void SceneDataTest::constructFieldTypeErased2DNonContiguous() { Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedInt)}}, SceneFieldType::Complex, Containers::StridedArrayView2D{rotationFieldData, {4, 2*sizeof(Complex)}}.every({1, 2})}; + /* SceneFieldType::Bit has no type-erased 2D field case, the constructor + taking a 2D bit array view is tested in + constructFieldArrayNonContiguous() below */ SceneFieldData{sceneFieldCustom(32), /* Just to cover the case of a 1-byte mapping type (lazy) */ Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedByte)}, {4, 1}}, @@ -1720,6 +2259,7 @@ void SceneDataTest::constructFieldTypeErased2DNonContiguous() { SceneFieldType::StringOffset8, Containers::StridedArrayView2D{helloFieldData, {4, 2*sizeof(UnsignedByte)}}.every({1, 2})}; CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" "Trade::SceneFieldData: second field view dimension is not contiguous\n" @@ -1729,13 +2269,19 @@ void SceneDataTest::constructFieldTypeErased2DNonContiguous() { void SceneDataTest::constructFieldArrayNonContiguous() { CORRADE_SKIP_IF_NO_ASSERT(); - UnsignedByte offsetMappingData[3]; + UnsignedByte mappingData[3]; Int offsetFieldData[3*4]; + char hiddenFieldData[2]{}; std::ostringstream out; Error redirectError{&out}; - SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetMappingData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}.every({1, 2})}; - CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: second field view dimension is not contiguous\n"); + SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(mappingData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}.every({1, 2})}; + SceneFieldData{sceneFieldCustom(773), + Containers::arrayView(mappingData), + Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData}, {3, 4}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second field view dimension is not contiguous\n" + "Trade::SceneFieldData: second field view dimension is not contiguous\n"); } void SceneDataTest::constructFieldArrayNotAllowed() { @@ -1749,7 +2295,9 @@ void SceneDataTest::constructFieldArrayNotAllowed() { auto rotationMappingChar = Containers::arrayCast<2, const char>(rotationMapping); auto rotationField2DChar = Containers::arrayCast<2, const char>(rotationFields2D); - /* This is all fine */ + /* This is all fine -- builtin fields can be created with the array + constructors if the array size is set to 0, custom fields of any type + can do anything */ SceneFieldData{SceneField::Rotation, SceneMappingType::UnsignedShort, rotationMapping, SceneFieldType::Quaternion, rotationField, 0}; @@ -1810,24 +2358,63 @@ void SceneDataTest::constructFieldArrayTypeErased2DWrongSize() { void SceneDataTest::constructFieldArrayTypeErased2DNonContiguous() { CORRADE_SKIP_IF_NO_ASSERT(); - char offsetMappingData[18*sizeof(UnsignedInt)]; + char mappingData[18*sizeof(UnsignedInt)]; char offsetFieldData[18*sizeof(Int)]; + char hiddenFieldData[2]{}; std::ostringstream out; Error redirectError{&out}; SceneFieldData{sceneFieldCustom(37), - Containers::StridedArrayView2D{offsetMappingData, {3, 2*sizeof(UnsignedInt)}}.every({1, 2}), + Containers::StridedArrayView2D{mappingData, {3, 2*sizeof(UnsignedInt)}}.every({1, 2}), SceneFieldType::Int, Containers::StridedArrayView2D{offsetFieldData, {3, 3*sizeof(Int)}}, 3}; + SceneFieldData{sceneFieldCustom(773), + Containers::StridedArrayView2D{mappingData, {3, sizeof(UnsignedInt)}}.every({1, 2}), + Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData}, {3, 4}}}; SceneFieldData{sceneFieldCustom(37), - Containers::StridedArrayView2D{offsetMappingData, {3, sizeof(UnsignedInt)}}, + Containers::StridedArrayView2D{mappingData, {3, sizeof(UnsignedInt)}}, SceneFieldType::Int, Containers::StridedArrayView2D{offsetFieldData, {3, 6*sizeof(Int)}}.every({1, 2}), 3}; + SceneFieldData{sceneFieldCustom(773), + Containers::StridedArrayView2D{mappingData, {3, sizeof(UnsignedInt)}}, + Containers::StridedBitArrayView2D{Containers::BitArrayView{hiddenFieldData}, {3, 4}}.every({1, 2})}; CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" + "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" + "Trade::SceneFieldData: second field view dimension is not contiguous\n" "Trade::SceneFieldData: second field view dimension is not contiguous\n"); } +void SceneDataTest::constructFieldBitTooLargeBitOffset() { + CORRADE_SKIP_IF_NO_DEBUG_ASSERT(); + + /* Only checked in the offset-only constructor, the StridedBitArrayView + checks this on its own already. There it's a debug-only assert, be + consistent and have it debug-only here as well. */ + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{sceneFieldCustom(773), 3, SceneMappingType::UnsignedInt, 0, sizeof(UnsignedInt), 0, 8, 1}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: bit offset expected to be smaller than 8, got 8\n"); +} + +void SceneDataTest::constructFieldBitTooLargeSize() { + CORRADE_SKIP_IF_NO_DEBUG_ASSERT(); + + /* Only checked in the offset-only constructor, the StridedBitArrayView + checks this on its own already. There it's a debug-only assert, be + consistent and have it debug-only here as well. */ + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{sceneFieldCustom(773), std::size_t{1} << (sizeof(std::size_t)*8 - 3), SceneMappingType::UnsignedInt, 0, sizeof(UnsignedInt), 0, 0, 1}; + #ifndef CORRADE_TARGET_32BIT + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: size expected to be smaller than 2^61 bits, got 2305843009213693952\n"); + #else + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: size expected to be smaller than 2^29 bits, got 536870912\n"); + #endif +} + void SceneDataTest::construct() { struct TransformParent { UnsignedShort object; @@ -2209,6 +2796,212 @@ void SceneDataTest::constructNotOwned() { CORRADE_COMPARE(scene.mutableField(0)[2], 0); } +void SceneDataTest::constructBit() { + struct Data { + UnsignedByte object; + /* Bit 3 is one field, 5-7 the other; both are then included as both + direct and offset-only fields, the array then also with a negative + stride */ + UnsignedByte bits; + } data[]{ + {4, 0x01 << 3 | 0x02 << 5}, + {0, 0x01 << 3 | 0x02 << 5}, + {9, 0x00 << 3 | 0x05 << 5}, + {2, 0x01 << 3 | 0x07 << 5}, + }; + auto view = Containers::stridedArrayView(data); + + constexpr SceneField bitField = sceneFieldCustom(0); + constexpr SceneField arrayField = sceneFieldCustom(1); + constexpr SceneField bitFieldOffsetOnly = sceneFieldCustom(10); + constexpr SceneField arrayFieldOffsetOnly = sceneFieldCustom(11); + + SceneData scene{SceneMappingType::UnsignedByte, 10, DataFlag::Mutable, data, { + SceneFieldData{bitField, view.slice(&Data::object), + Containers::StridedBitArrayView1D{Containers::BitArrayView{data}, &data[0].bits, 3, 4, sizeof(Data)*8}, SceneFieldFlag::ImplicitMapping}, + SceneFieldData{arrayField, view.slice(&Data::object), + Containers::StridedBitArrayView2D{Containers::BitArrayView{data}, &data[0].bits, 5, {4, 3}, {sizeof(Data)*8, 1}}, SceneFieldFlag::OrderedMapping}, + /* The two above, just as offset-only */ + SceneFieldData{bitFieldOffsetOnly, 4, + SceneMappingType::UnsignedByte, 0, sizeof(Data), + offsetof(Data, bits), 3, sizeof(Data)*8, SceneFieldFlag::ImplicitMapping}, + SceneFieldData{arrayFieldOffsetOnly, 4, + SceneMappingType::UnsignedByte, 0, sizeof(Data), + offsetof(Data, bits), 5, sizeof(Data)*8, 3, SceneFieldFlag::OrderedMapping}, + }}; + + /* Raw field data access has special handling for bits, as offset-only + fields are always converted to real views */ + for(UnsignedInt i: {0, 2}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldData(i).flags(), SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(scene.fieldData(i).mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(scene.fieldData(i).mappingData().data(), &data[0].object); + CORRADE_COMPARE(scene.fieldData(i).mappingData().size(), 4); + CORRADE_COMPARE(scene.fieldData(i).mappingData().stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(scene.fieldData(i).mappingData()), + Containers::arrayView({4, 0, 9, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldData(i).fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(scene.fieldData(i).fieldArraySize(), 0); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().data(), &data[0].bits); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().offset(), 3); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().size(), (Containers::Size2D{4, 1})); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((scene.fieldData(i).fieldBitData().transposed<0, 1>())[0], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + } + for(UnsignedInt i: {1, 3}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldData(i).flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldData(i).mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(scene.fieldData(i).mappingData().data(), &data[0].object); + CORRADE_COMPARE(scene.fieldData(i).mappingData().size(), 4); + CORRADE_COMPARE(scene.fieldData(i).mappingData().stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(scene.fieldData(i).mappingData()), + Containers::arrayView({4, 0, 9, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldData(i).fieldType(), SceneFieldType::Bit); + CORRADE_COMPARE(scene.fieldData(i).fieldArraySize(), 3); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().data(), &data[0].bits); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().offset(), 5); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + /* Testing just the first slice, should be enough. The whole data is + tested properly below */ + CORRADE_COMPARE_AS((scene.fieldData(i).fieldBitData().transposed<0, 1>())[0], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + } + + /* Field propery access -- nothing special is done for bits, so just verify + roughly that the calls work */ + CORRADE_COMPARE(scene.fieldFlags(bitFieldOffsetOnly), SceneFieldFlag::OffsetOnly|SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(scene.fieldType(arrayField), SceneFieldType::Bit); + CORRADE_COMPARE(scene.fieldArraySize(arrayFieldOffsetOnly), 3); + + /* Single-bit field access using an ID and a name. The view and offset-only + variants should give the same results, const and mutable variant as + well, the array variant should working here too. */ + for(UnsignedInt i: {0, 2}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldBits(i).size(), 4); + CORRADE_COMPARE(scene.fieldBits(i).stride(), sizeof(Data)*8); + CORRADE_COMPARE_AS(scene.fieldBits(i), Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBits(i).size(), 4); + CORRADE_COMPARE(scene.mutableFieldBits(i).stride(), sizeof(Data)*8); + CORRADE_COMPARE_AS(scene.mutableFieldBits(i), Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldBitArrays(i).size(), (Containers::Size2D{4, 1})); + CORRADE_COMPARE(scene.fieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).size(), (Containers::Size2D{4, 1})); + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + } + for(SceneField i: {bitField, bitFieldOffsetOnly}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldBits(i).size(), 4); + CORRADE_COMPARE(scene.fieldBits(i).stride(), sizeof(Data)*8); + CORRADE_COMPARE_AS(scene.fieldBits(i), Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBits(i).size(), 4); + CORRADE_COMPARE(scene.mutableFieldBits(i).stride(), sizeof(Data)*8); + CORRADE_COMPARE_AS(scene.mutableFieldBits(i), Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldBitArrays(i).size(), (Containers::Size2D{4, 1})); + CORRADE_COMPARE(scene.fieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).size(), (Containers::Size2D{4, 1})); + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + } + + /* Bit array field access using ID and name. The view and offset-only + variants should give the same results, const and mutable variant as + well. */ + for(UnsignedInt i: {1, 3}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldBitArrays(i).size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.fieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + /* Taking all first array items, all second etc, because that's less + comparisons than comparing 3 bits 4 times */ + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[1], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[2], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + /* Taking all first array items, all second etc, because that's less + comparisons than comparing 3 bits 4 times */ + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[1], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[2], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + } + for(SceneField i: {arrayField, arrayFieldOffsetOnly}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldBitArrays(i).size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.fieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + /* Taking all first array items, all second etc, because that's less + comparisons than comparing 3 bits 4 times */ + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[1], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[2], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).stride(), (Containers::Stride2D{sizeof(Data)*8, 1})); + /* Taking all first array items, all second etc, because that's less + comparisons than comparing 3 bits 4 times */ + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[0], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[1], Containers::stridedArrayView({ + true, true, false, true + }).sliceBit(0), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[2], Containers::stridedArrayView({ + false, false, true, true + }).sliceBit(0), TestSuite::Compare::Container); + } +} + template struct StringFieldTraits; template<> struct StringFieldTraits { static const char* name() { return "8"; } @@ -2576,6 +3369,109 @@ void SceneDataTest::constructSpecialStrides() { TestSuite::Compare::Container); } +void SceneDataTest::constructSpecialStridesBit() { + auto&& instanceData = ConstructSpecialStridesBitData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Similar to constructBit(), except that the strides are negative and + zero, and only properties related to those are tested */ + + struct Data { + UnsignedByte object; + /* Bit 3 is one field, 5-7 the other; both are then included as both + direct and offset-only fields, the array then also with a negative + stride */ + UnsignedByte bits; + } data[]{ + {4, 0x01 << 3 | 0x02 << 5}, + {0, 0x01 << 3 | 0x02 << 5}, + {9, 0x00 << 3 | 0x05 << 5}, + {2, 0x01 << 3 | 0x07 << 5}, + }; + auto view = Containers::stridedArrayView(data); + + constexpr SceneField bitField = sceneFieldCustom(0); + constexpr SceneField arrayField = sceneFieldCustom(1); + constexpr SceneField bitFieldOffsetOnly = sceneFieldCustom(10); + constexpr SceneField arrayFieldOffsetOnly = sceneFieldCustom(11); + + SceneData scene{SceneMappingType::UnsignedByte, 10, DataFlag::Mutable, data, { + SceneFieldData{bitField, view.slice(&Data::object), + Containers::StridedBitArrayView1D{Containers::BitArrayView{data}, &data[0].bits + instanceData.offset, instanceData.bitOffset, 4, instanceData.stride}, SceneFieldFlag::ImplicitMapping}, + SceneFieldData{arrayField, view.slice(&Data::object), + Containers::StridedBitArrayView2D{Containers::BitArrayView{data}, &data[0].bits + instanceData.offset, instanceData.arrayBitOffset, {4, 3}, {instanceData.stride, 1}}, SceneFieldFlag::OrderedMapping}, + /* The two above, just as offset-only */ + SceneFieldData{bitFieldOffsetOnly, 4, + SceneMappingType::UnsignedByte, 0, sizeof(Data), + offsetof(Data, bits) + instanceData.offset, instanceData.bitOffset, instanceData.stride, SceneFieldFlag::ImplicitMapping}, + SceneFieldData{arrayFieldOffsetOnly, 4, + SceneMappingType::UnsignedByte, 0, sizeof(Data), + offsetof(Data, bits) + instanceData.offset, instanceData.arrayBitOffset, instanceData.stride, 3, SceneFieldFlag::OrderedMapping}, + }}; + + /* Raw field data access */ + for(UnsignedInt i: {0, 2}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().size(), (Containers::Size2D{4, 1})); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().stride(), (Containers::Stride2D{instanceData.stride, 1})); + CORRADE_COMPARE_AS((scene.fieldData(i).fieldBitData().transposed<0, 1>())[0], + (Containers::BitArrayView{&instanceData.expectedBits, 0, 4}), + TestSuite::Compare::Container); + } + for(UnsignedInt i: {1, 3}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.fieldData(i).fieldBitData().stride(), (Containers::Stride2D{instanceData.stride, 1})); + /* Testing just the first slice, should be enough. The whole data is + tested properly below */ + CORRADE_COMPARE_AS((scene.fieldData(i).fieldBitData().transposed<0, 1>())[0], + (Containers::BitArrayView{&instanceData.expectedArrayBits[0], 0, 4}), + TestSuite::Compare::Container); + } + + /* Bit fields */ + for(SceneField i: {bitField, bitFieldOffsetOnly}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldBits(i).stride(), instanceData.stride); + CORRADE_COMPARE_AS(scene.fieldBits(i), + (Containers::BitArrayView{&instanceData.expectedBits, 0, 4}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBits(i).stride(), instanceData.stride); + CORRADE_COMPARE_AS(scene.mutableFieldBits(i), + (Containers::BitArrayView{&instanceData.expectedBits, 0, 4}), + TestSuite::Compare::Container); + } + + /* Bit array fields */ + for(SceneField i: {arrayField, arrayFieldOffsetOnly}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(scene.fieldBitArrays(i).size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.fieldBitArrays(i).stride(), (Containers::Stride2D{instanceData.stride, 1})); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[0], + (Containers::BitArrayView{&instanceData.expectedArrayBits[0], 0, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[1], + (Containers::BitArrayView{&instanceData.expectedArrayBits[1], 0, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.fieldBitArrays(i).transposed<0, 1>())[2], + (Containers::BitArrayView{&instanceData.expectedArrayBits[0], 0, 4}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).size(), (Containers::Size2D{4, 3})); + CORRADE_COMPARE(scene.mutableFieldBitArrays(i).stride(), (Containers::Stride2D{instanceData.stride, 1})); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[0], + (Containers::BitArrayView{&instanceData.expectedArrayBits[0], 0, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[1], + (Containers::BitArrayView{&instanceData.expectedArrayBits[1], 0, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.mutableFieldBitArrays(i).transposed<0, 1>())[2], + (Containers::BitArrayView{&instanceData.expectedArrayBits[0], 0, 4}), + TestSuite::Compare::Container); + } +} + #ifdef MAGNUM_BUILD_DEPRECATED void SceneDataTest::constructDeprecated() { auto&& data = ChildrenDeprecatedData[testCaseInstanceId()]; @@ -2726,7 +3622,9 @@ void SceneDataTest::constructFieldDataNotContained() { CORRADE_SKIP_IF_NO_ASSERT(); /* Mostly the same as constructMappingDataNotContained() with mapping and - field views swapped, and added checks for array fields */ + field views swapped, and added checks for array fields. Bit fields have + many special cases on their own and are tested in + constructBitFieldDataNotContained() below. */ const Containers::Array data{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; Containers::Array sameDataButMovable{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; @@ -2783,6 +3681,107 @@ void SceneDataTest::constructFieldDataNotContained() { "Trade::SceneData: offset-only field data of field 0 span 25 bytes but passed data array has only 24\n"); } +void SceneDataTest::constructBitFieldDataNotContained() { + CORRADE_SKIP_IF_NO_ASSERT(); + + /* Tested separately from constructFieldDataNotContained() as there's + enough bit-related cases on its own. It goes through the same variants, + but explicitly checks also non-zero bit offset. */ + + const Containers::Array data{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; + Containers::Array sameDataButMovable{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; + Containers::ArrayView mappingData{reinterpret_cast(0xbadda9), 10}; + Containers::StridedBitArrayView1D dataOneByteOut{Containers::BitArrayView{reinterpret_cast(0xbaddaa), 0, 80}, 10, 8}; + Containers::StridedBitArrayView1D dataTwoBitsOut{Containers::BitArrayView{reinterpret_cast(0xbadda9), 0, 90}, 10, 9}; + Containers::StridedBitArrayView1D dataOneBitOffsetOut{Containers::BitArrayView{reinterpret_cast(0xbadda9), 4, 95}, 5, 19}; + Containers::StridedBitArrayView1D dataOneBitOffsetBeforeOut{Containers::BitArrayView{reinterpret_cast(0xbadda8), 7, 80}, 10, 8}; + Containers::BitArrayView dataOut{reinterpret_cast(0xdead), 7, 10}; + + std::ostringstream out; + Error redirectError{&out}; + /* Basic "obviously wrong" case with owned data */ + SceneData{SceneMappingType::UnsignedByte, 10, std::move(sameDataButMovable), { + /* This is here to test that not just the first attribute gets checked + and that the message shows proper ID */ + SceneFieldData{SceneField::Light, mappingData, mappingData}, + SceneFieldData{sceneFieldCustom(773), mappingData, dataOut} + }}; + /* A "slightly off" view that exceeds the original by one byte and two bits, + respectively */ + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData, dataOneByteOut} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData, dataTwoBitsOut} + }}; + /* Verify the bit offset is taken into account. On begin it shouldn't be + rounded up, otherwise the second variant would pass.*/ + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData.prefix(5), dataOneBitOffsetOut} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData, dataOneBitOffsetBeforeOut} + }}; + /* Verify array size is taken into account as well. If not, the data would + span only 9 bytes instead of 11, which would pass. */ + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData.prefix(9), Containers::StridedBitArrayView2D{Containers::BitArrayView{reinterpret_cast(0xbadda9), 0, 81}, {9, 9}}} + }}; + /* Not checking for nullptr data, since that got checked for mapping view + already and there's no way to trigger it for fields */ + /* Offset-only fields with a different message, again both one byte and one + bit off, one bit with offset */ + SceneData{SceneMappingType::UnsignedByte, 10, Containers::Array{10}, { + SceneFieldData{sceneFieldCustom(773), 10, SceneMappingType::UnsignedByte, 0, 1, 1, 0, 8} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, Containers::Array{10}, { + SceneFieldData{sceneFieldCustom(773), 10, SceneMappingType::UnsignedByte, 0, 1, 0, 0, 9} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, Containers::Array{10}, { + SceneFieldData{sceneFieldCustom(773), 5, SceneMappingType::UnsignedByte, 0, 1, 0, 4, 19} + }}; + /* One with array */ + SceneData{SceneMappingType::UnsignedByte, 10, Containers::Array{10}, { + SceneFieldData{sceneFieldCustom(773), 9, SceneMappingType::UnsignedByte, 0, 1, 0, 0, 9, 9} + }}; + /* And the final boss, negative strides -- one byte off and two bits off. + Both only caught if the element size gets properly added to the larger + offset, not just the "end". */ + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData, dataOneByteOut.flipped<0>()} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, {}, data, { + SceneFieldData{sceneFieldCustom(773), mappingData, dataTwoBitsOut.flipped<0>()} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, Containers::Array{10}, { + SceneFieldData{sceneFieldCustom(773), 10, SceneMappingType::UnsignedByte, 0, 1, 10, 0, -8} + }}; + SceneData{SceneMappingType::UnsignedByte, 10, Containers::Array{10}, { + SceneFieldData{sceneFieldCustom(773), 9, SceneMappingType::UnsignedByte, 0, 1, 10, 0, -9} + }}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: field data [0xdead:0xdeb0] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + + "Trade::SceneData: field data [0xbaddaa:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xbadda9:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xbadda9:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xbadda8:0xbaddb2] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + + "Trade::SceneData: field data [0xbadda9:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + + "Trade::SceneData: offset-only field data of field 0 span 11 bytes but passed data array has only 10\n" + "Trade::SceneData: offset-only field data of field 0 span 11 bytes but passed data array has only 10\n" + "Trade::SceneData: offset-only field data of field 0 span 11 bytes but passed data array has only 10\n" + + "Trade::SceneData: offset-only field data of field 0 span 11 bytes but passed data array has only 10\n" + + "Trade::SceneData: field data [0xbaddaa:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xbadda9:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: offset-only field data of field 0 span 11 bytes but passed data array has only 10\n" + "Trade::SceneData: offset-only field data of field 0 span 11 bytes but passed data array has only 10\n" + ); +} + void SceneDataTest::constructStringDataNotContained() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -5530,6 +6529,11 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(2); scene.mutableField(2); + scene.fieldBits(2); + scene.fieldBitArrays(2); + scene.mutableFieldBits(2); + scene.mutableFieldBitArrays(2); + scene.fieldStringData(2); scene.fieldStrings(2); @@ -5548,6 +6552,11 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(sceneFieldCustom(666)); scene.mutableField(sceneFieldCustom(666)); + scene.fieldBits(sceneFieldCustom(666)); + scene.fieldBitArrays(sceneFieldCustom(666)); + scene.mutableFieldBits(sceneFieldCustom(666)); + scene.mutableFieldBitArrays(sceneFieldCustom(666)); + scene.fieldStringData(sceneFieldCustom(666)); scene.fieldStrings(sceneFieldCustom(666)); @@ -5599,6 +6608,11 @@ void SceneDataTest::fieldNotFound() { "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldBits(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldBitArrays(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableFieldBits(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableFieldBitArrays(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldStringData(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldStrings(): index 2 out of range for 2 fields\n" @@ -5617,6 +6631,11 @@ void SceneDataTest::fieldNotFound() { "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldBits(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldBitArrays(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableFieldBits(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableFieldBitArrays(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldStringData(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::fieldStrings(): field Trade::SceneField::Custom(666) not found\n" @@ -5663,6 +6682,7 @@ void SceneDataTest::fieldWrongType() { UnsignedInt object; UnsignedShort foobar; UnsignedShort mesh; + bool yes; } fields[2]{}; Containers::StridedArrayView1D view = fields; @@ -5670,14 +6690,26 @@ void SceneDataTest::fieldWrongType() { SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, fields, { SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + SceneFieldData{sceneFieldCustom(773), view.slice(&Field::object), view.slice(&Field::yes).sliceBit(0)}, }}; std::ostringstream out; Error redirectError{&out}; + scene.field(2); + scene.mutableField(2); + scene.field(1); scene.field(1); scene.mutableField(1); scene.mutableField(1); + /* Accessing SceneFieldType::Bit through field(2) etc. is handled by the + same code path as accessing via a mismatched type, no need to test those + explicitly */ + + scene.fieldBits(1); + scene.fieldBitArrays(1); + scene.mutableFieldBits(1); + scene.mutableFieldBitArrays(1); scene.fieldStringData(1); scene.fieldStrings(1); @@ -5687,15 +6719,34 @@ void SceneDataTest::fieldWrongType() { scene.mutableField(SceneField::Mesh); scene.mutableField(SceneField::Mesh); + scene.field(sceneFieldCustom(773)); + scene.mutableField(sceneFieldCustom(773)); + /* Accessing SceneFieldType::Bit through field(2) etc. is handled by the + same code path as accessing via a mismatched type, no need to test those + explicitly */ + + scene.fieldBits(SceneField::Mesh); + scene.fieldBitArrays(SceneField::Mesh); + scene.mutableFieldBits(SceneField::Mesh); + scene.mutableFieldBitArrays(SceneField::Mesh); + scene.fieldStringData(SceneField::Mesh); scene.fieldStrings(SceneField::Mesh); CORRADE_COMPARE(out.str(), + "Trade::SceneData::field(): Trade::SceneField::Custom(773) is Trade::SceneFieldType::Bit, use fieldBits() or fieldBitArrays() to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(773) is Trade::SceneFieldType::Bit, use mutableFieldBits() or mutableFieldBitArrays() to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::fieldBits(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::fieldBitArrays(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::mutableFieldBits(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::mutableFieldBitArrays(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::fieldStringData(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a string\n" "Trade::SceneData::fieldStrings(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a string\n" @@ -5704,6 +6755,14 @@ void SceneDataTest::fieldWrongType() { "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(773) is Trade::SceneFieldType::Bit, use fieldBits() or fieldBitArrays() to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(773) is Trade::SceneFieldType::Bit, use mutableFieldBits() or mutableFieldBitArrays() to access it\n" + + "Trade::SceneData::fieldBits(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::fieldBitArrays(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::mutableFieldBits(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::mutableFieldBitArrays(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a bit\n" + "Trade::SceneData::fieldStringData(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a string\n" "Trade::SceneData::fieldStrings(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort, not a string\n"); } @@ -5784,16 +6843,18 @@ void SceneDataTest::fieldWrongArrayAccess() { struct Field { UnsignedInt object; UnsignedInt foobar; + bool yes; } fields[2]{}; Containers::StridedArrayView1D view = fields; SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, fields, { SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), Containers::arrayCast<2, UnsignedInt>(view.slice(&Field::foobar))}, + SceneFieldData{sceneFieldCustom(773), view.slice(&Field::object), Containers::StridedBitArrayView2D{Containers::BitArrayView{fields}, &fields[0].yes, 0, {2, 3}, {sizeof(Field)*8, 1}}}, }}; /* Array access is allowed for non-array fields (the second dimension is - then always 1), tested directly in construct() */ + then always 1), tested directly in construct() and constructBit() */ std::ostringstream out; Error redirectError{&out}; @@ -5801,11 +6862,19 @@ void SceneDataTest::fieldWrongArrayAccess() { scene.mutableField(0); scene.field(sceneFieldCustom(35)); scene.mutableField(sceneFieldCustom(35)); + scene.fieldBits(1); + scene.mutableFieldBits(1); + scene.fieldBits(sceneFieldCustom(773)); + scene.mutableFieldBits(sceneFieldCustom(773)); CORRADE_COMPARE(out.str(), "Trade::SceneData::field(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" "Trade::SceneData::field(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" - "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n"); + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::fieldBits(): Trade::SceneField::Custom(773) is an array field, use fieldBitArrays() to access it\n" + "Trade::SceneData::mutableFieldBits(): Trade::SceneField::Custom(773) is an array field, use fieldBitArrays() to access it\n" + "Trade::SceneData::fieldBits(): Trade::SceneField::Custom(773) is an array field, use fieldBitArrays() to access it\n" + "Trade::SceneData::mutableFieldBits(): Trade::SceneField::Custom(773) is an array field, use fieldBitArrays() to access it\n"); } void SceneDataTest::parentFor() {