From ae6029d128743adf5fff0fbe8151182b1ca40bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 3 Dec 2022 21:32:06 +0100 Subject: [PATCH] Trade: support storing string fields in SceneData. Finally got an idea how to provide various options store these efficiently, so it's implemented now. Five different storage variants times four different type sizes. --- doc/snippets/MagnumTrade.cpp | 58 ++ src/Magnum/Trade/SceneData.cpp | 404 +++++++++--- src/Magnum/Trade/SceneData.h | 703 +++++++++++++++++++-- src/Magnum/Trade/Test/SceneDataTest.cpp | 799 +++++++++++++++++++++++- 4 files changed, 1814 insertions(+), 150 deletions(-) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index fef9368e8..5146bc33f 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include /** @todo remove once file callbacks are -free */ #include #include @@ -976,6 +977,17 @@ Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 120, std::move(data), /* [SceneFieldData-usage-offset-only] */ } +{ +/* [SceneFieldData-usage-strings] */ +Containers::StridedArrayView1D mapping = DOXYGEN_ELLIPSIS({}); +Containers::StringView string = DOXYGEN_ELLIPSIS({}); +Containers::StridedArrayView1D> ranges = DOXYGEN_ELLIPSIS({}); + +Trade::SceneFieldData field{Trade::sceneFieldCustom(35), mapping, + string.data(), Trade::SceneFieldType::StringRange32, ranges}; +/* [SceneFieldData-usage-strings] */ +} + { typedef SceneGraph::Scene Scene3D; typedef SceneGraph::Object Object3D; @@ -1162,4 +1174,50 @@ static_cast(cellFrustums); static_cast(cellLights); } +{ +using namespace Containers::Literals; +/* [SceneData-populating-strings] */ +constexpr Containers::StringView CategoryStrings = + "wall\0furniture\0lighting\0artwork"_s; +constexpr UnsignedByte CategoryWall = 0; +constexpr UnsignedByte CategoryFurniture = 5; +constexpr UnsignedByte CategoryLighting = 15; +constexpr UnsignedByte CategoryArtwork = 24; + +Containers::MutableStringView categoryStrings; +Containers::ArrayView mapping; +Containers::ArrayView categories; +Containers::ArrayTuple data{ + {CategoryStrings.size(), categoryStrings}, + {DOXYGEN_ELLIPSIS(5), mapping}, + {DOXYGEN_ELLIPSIS(5), categories}, +}; + +Utility::copy(CategoryStrings, categoryStrings); +mapping[0] = 7; +categories[0] = CategoryWall; +mapping[1] = 19; +categories[1] = CategoryFurniture; +DOXYGEN_ELLIPSIS(static_cast(CategoryLighting); static_cast(CategoryArtwork);) + +constexpr Trade::SceneField SceneFieldCategory = Trade::sceneFieldCustom(25); +Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, DOXYGEN_ELLIPSIS(5), std::move(data), { + Trade::SceneFieldData{SceneFieldCategory, mapping, + categoryStrings.data(), Trade::SceneFieldType::StringRangeNullTerminated8, + categories} +}}; +/* [SceneData-populating-strings] */ +} + +{ +constexpr Trade::SceneField SceneFieldCategory = Trade::sceneFieldCustom(25); +Trade::SceneData scene{{}, 0, nullptr, nullptr}; +/* [SceneData-populating-strings-retrieve] */ +Containers::StringIterable categories = scene.fieldStrings(SceneFieldCategory); + +// Prints "furniture" +Debug{} << categories[scene.fieldObjectOffset(SceneFieldCategory, 19)]; +/* [SceneData-populating-strings-retrieve] */ +} + } diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 29b4ad022..839036ec0 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include @@ -238,6 +240,18 @@ Debug& operator<<(Debug& debug, const SceneFieldType value) { _c(Radd) _c(Pointer) _c(MutablePointer) + _c(StringOffset8) + _c(StringOffset16) + _c(StringOffset32) + _c(StringOffset64) + _c(StringRange8) + _c(StringRange16) + _c(StringRange32) + _c(StringRange64) + _c(StringRangeNullTerminated8) + _c(StringRangeNullTerminated16) + _c(StringRangeNullTerminated32) + _c(StringRangeNullTerminated64) #undef _c /* LCOV_EXCL_STOP */ } @@ -253,6 +267,8 @@ UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { switch(type) { case SceneFieldType::UnsignedByte: case SceneFieldType::Byte: + case SceneFieldType::StringOffset8: + case SceneFieldType::StringRangeNullTerminated8: return 1; case SceneFieldType::UnsignedShort: case SceneFieldType::Short: @@ -261,6 +277,9 @@ UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { case SceneFieldType::Vector2b: case SceneFieldType::Degh: case SceneFieldType::Radh: + case SceneFieldType::StringOffset16: + case SceneFieldType::StringRange8: + case SceneFieldType::StringRangeNullTerminated16: return 2; case SceneFieldType::Vector3ub: case SceneFieldType::Vector3b: @@ -276,6 +295,9 @@ UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { case SceneFieldType::Range1Dh: case SceneFieldType::Deg: case SceneFieldType::Rad: + case SceneFieldType::StringOffset32: + case SceneFieldType::StringRange16: + case SceneFieldType::StringRangeNullTerminated32: return 4; case SceneFieldType::Vector3us: case SceneFieldType::Vector3s: @@ -297,6 +319,9 @@ UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { case SceneFieldType::Complex: case SceneFieldType::Degd: case SceneFieldType::Radd: + case SceneFieldType::StringOffset64: + case SceneFieldType::StringRange32: + case SceneFieldType::StringRangeNullTerminated64: return 8; case SceneFieldType::Vector3: case SceneFieldType::Vector3ui: @@ -318,6 +343,7 @@ UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { case SceneFieldType::Complexd: case SceneFieldType::DualComplex: case SceneFieldType::Quaternion: + case SceneFieldType::StringRange64: return 16; case SceneFieldType::Matrix3x3h: return 18; @@ -384,6 +410,9 @@ UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { case SceneFieldType::Vector2b: case SceneFieldType::Vector3b: case SceneFieldType::Vector4b: + case SceneFieldType::StringOffset8: + case SceneFieldType::StringRange8: + case SceneFieldType::StringRangeNullTerminated8: return 1; case SceneFieldType::UnsignedShort: case SceneFieldType::Vector2us: @@ -411,6 +440,9 @@ UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { case SceneFieldType::Range3Dh: case SceneFieldType::Degh: case SceneFieldType::Radh: + case SceneFieldType::StringOffset16: + case SceneFieldType::StringRange16: + case SceneFieldType::StringRangeNullTerminated16: return 2; case SceneFieldType::UnsignedInt: case SceneFieldType::Vector2ui: @@ -445,6 +477,9 @@ UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { case SceneFieldType::DualQuaternion: case SceneFieldType::Deg: case SceneFieldType::Rad: + case SceneFieldType::StringOffset32: + case SceneFieldType::StringRange32: + case SceneFieldType::StringRangeNullTerminated32: return 4; case SceneFieldType::UnsignedLong: case SceneFieldType::Long: @@ -470,6 +505,9 @@ UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { case SceneFieldType::DualQuaterniond: case SceneFieldType::Degd: case SceneFieldType::Radd: + case SceneFieldType::StringOffset64: + case SceneFieldType::StringRange64: + case SceneFieldType::StringRangeNullTerminated64: return 8; case SceneFieldType::Pointer: case SceneFieldType::MutablePointer: @@ -494,6 +532,7 @@ Debug& operator<<(Debug& debug, const SceneFieldFlag value) { _c(OffsetOnly) _c(ImplicitMapping) _c(OrderedMapping) + _c(NullTerminatedString) #undef _c /* LCOV_EXCL_STOP */ } @@ -506,7 +545,8 @@ Debug& operator<<(Debug& debug, const SceneFieldFlags value) { SceneFieldFlag::OffsetOnly, SceneFieldFlag::ImplicitMapping, /* This one is implied by ImplicitMapping, so has to be after */ - SceneFieldFlag::OrderedMapping + SceneFieldFlag::OrderedMapping, + SceneFieldFlag::NullTerminatedString }); } @@ -521,10 +561,37 @@ SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedA "Trade::SceneFieldData: second field view dimension size" << fieldData.size()[1] << "doesn't match" << fieldType, ); #endif - if(mappingData.size()[1] == 8) _mappingType = SceneMappingType::UnsignedLong; - else if(mappingData.size()[1] == 4) _mappingType = SceneMappingType::UnsignedInt; - else if(mappingData.size()[1] == 2) _mappingType = SceneMappingType::UnsignedShort; - else if(mappingData.size()[1] == 1) _mappingType = SceneMappingType::UnsignedByte; + 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", ); + CORRADE_ASSERT(fieldData.isContiguous<1>(), "Trade::SceneFieldData: second field view dimension is not contiguous", ); +} + +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const char* const stringData, const SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, {}, Containers::StridedArrayView1D{{mappingData.data(), ~std::size_t{}}, mappingData.size()[0], mappingData.stride()[0]}, stringData, fieldType, Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, 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.isEmpty()[0] || fieldData.size()[1] == sceneFieldTypeSize(fieldType), + "Trade::SceneFieldData: second field view dimension size" << fieldData.size()[1] << "doesn't match" << fieldType, ); + + /* Merge the mapping type with the string type already writen by the + delegated-to constructor -- that's why the |=. */ + 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", ); @@ -553,14 +620,49 @@ Containers::StridedArrayView1D SceneFieldData::fieldData() const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - {_fieldData.pointer, ~std::size_t{}}, _size, _fieldStride}; + {_fieldData.pointer, ~std::size_t{}}, _size, _field.data.stride}; } Containers::StridedArrayView1D SceneFieldData::fieldData(const Containers::ArrayView data) const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - data, _flags & SceneFieldFlag::OffsetOnly ? static_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _fieldStride}; + data, _flags & SceneFieldFlag::OffsetOnly ? static_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _field.data.stride}; +} + +namespace { + +/* https://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend ; + stride extraction doesn't need a helper as we can access the union field + directly. It's also not directly using bitfields in the SceneFieldData + privates as I expect nasty portability / endian issues and compiler warnings + coming from this. */ +inline Long extractStringFieldOffset(const UnsignedLong strideOffset) { + union { + struct { + Short:16; + Long offset:48; + } s; + UnsignedLong u; + } caster; + caster.u = strideOffset; + return caster.s.offset; +} + +} + +const char* SceneFieldData::stringData() const { + CORRADE_ASSERT(_mappingTypeStringType & Implementation::SceneMappingStringTypeMask, + "Trade::SceneFieldData::stringData(): the field is" << _field.data.type << Debug::nospace << ", not a string", {}); + CORRADE_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), + "Trade::SceneFieldData::stringData(): the field is offset-only, supply a data array", {}); + return static_cast(_fieldData.pointer) + extractStringFieldOffset(_field.strideOffset); +} + +const char* SceneFieldData::stringData(Containers::ArrayView data) const { + CORRADE_ASSERT(_mappingTypeStringType & Implementation::SceneMappingStringTypeMask, + "Trade::SceneFieldData::stringData(): the field is" << _field.data.type << Debug::nospace << ", not a string", {}); + return static_cast(_flags & SceneFieldFlag::OffsetOnly ? data.data() : _fieldData.pointer) + extractStringFieldOffset(_field.strideOffset); } Containers::Array sceneFieldDataNonOwningArray(const Containers::ArrayView view) { @@ -603,8 +705,8 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp large enough to reference all objects (checked outside of the loop above) and that it's the same for all fields. This also makes it more convenient for the user. */ - CORRADE_ASSERT(field._mappingType == _mappingType, - "Trade::SceneData: inconsistent mapping type, got" << field._mappingType << "for field" << i << "but expected" << _mappingType, ); + CORRADE_ASSERT(field.mappingType() == _mappingType, + "Trade::SceneData: inconsistent mapping type, got" << field.mappingType() << "for field" << i << "but expected" << _mappingType, ); /* We could check that object indices are in bounds, but that's rather expensive. OTOH it's fine if field size is larger than object count, @@ -631,8 +733,8 @@ 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) { - const UnsignedInt fieldTypeSize = sceneFieldTypeSize(field._fieldType)* - (field._fieldArraySize ? field._fieldArraySize : 1); + 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 @@ -641,7 +743,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp 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._fieldStride; + 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); @@ -660,6 +762,15 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp CORRADE_ASSERT(reinterpret_cast(fieldBegin) >= _data.begin() && reinterpret_cast(fieldEnd) <= _data.end(), "Trade::SceneData: field data [" << Debug::nospace << reinterpret_cast(fieldBegin) << Debug::nospace << ":" << Debug::nospace << reinterpret_cast(fieldEnd) << Debug::nospace << "] of field" << i << "are not contained in passed data array [" << Debug::nospace << static_cast(_data.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_data.end()) << Debug::nospace << "]", ); } + + /* If a string field, check that the string data pointer is in + bounds. Not checking the offsets/sizes in the field itself + though, as that'd be prohibitively expensive. */ + if(field._mappingTypeStringType & Implementation::SceneMappingStringTypeMask) { + const char* const stringData = field.stringData(_data); + CORRADE_ASSERT(stringData >= _data.begin() && stringData <= _data.end(), + "Trade::SceneData: field string data" << reinterpret_cast(stringData) << "of field" << i << "are not contained in passed data array [" << Debug::nospace << static_cast(_data.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_data.end()) << Debug::nospace << "]", ); + } } #endif @@ -693,7 +804,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp "begin" was always zero, here we're always comparing four values, so the message for offset-only wouldn't be simpler either. */ const auto checkFieldMappingDataMatch = [](const SceneFieldData& a, const SceneFieldData& b) { - const std::size_t mappingTypeSize = sceneMappingTypeSize(a._mappingType); + const std::size_t mappingTypeSize = sceneMappingTypeSize(a.mappingType()); const void* const aBegin = a._mappingData.pointer; const void* const bBegin = b._mappingData.pointer; const void* const aEnd = static_cast(a._mappingData.pointer) + a._size*mappingTypeSize; @@ -719,7 +830,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp /* Decide about dimensionality based on transformation type, if present */ if(transformationField != ~UnsignedInt{}) { - const SceneFieldType fieldType = _fields[transformationField]._fieldType; + const SceneFieldType fieldType = _fields[transformationField]._field.data.type; if(fieldType == SceneFieldType::Matrix3x3 || fieldType == SceneFieldType::Matrix3x3d || fieldType == SceneFieldType::Matrix3x2 || @@ -740,7 +851,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp /* Use TRS fields to decide about dimensionality, if the transformation field is not present. If it is, verify that they match it. */ if(translationField != ~UnsignedInt{}) { - const SceneFieldType fieldType = _fields[translationField]._fieldType; + const SceneFieldType fieldType = _fields[translationField]._field.data.type; if(fieldType == SceneFieldType::Vector2 || fieldType == SceneFieldType::Vector2d) { CORRADE_ASSERT(!_dimensions || _dimensions == 2, @@ -754,7 +865,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } if(rotationField != ~UnsignedInt{}) { - const SceneFieldType fieldType = _fields[rotationField]._fieldType; + const SceneFieldType fieldType = _fields[rotationField]._field.data.type; if(fieldType == SceneFieldType::Complex || fieldType == SceneFieldType::Complexd) { CORRADE_ASSERT(!_dimensions || _dimensions == 2, @@ -768,7 +879,7 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } if(scalingField != ~UnsignedInt{}) { - const SceneFieldType fieldType = _fields[scalingField]._fieldType; + const SceneFieldType fieldType = _fields[scalingField]._field.data.type; if(fieldType == SceneFieldType::Vector2 || fieldType == SceneFieldType::Vector2d) { CORRADE_ASSERT(!_dimensions || _dimensions == 2, @@ -875,8 +986,8 @@ Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal /* We're *sure* the view is correct, so faking the view size */ {static_cast(field._flags & SceneFieldFlag::OffsetOnly ? _data.data() + field._fieldData.offset : field._fieldData.pointer) - + field._fieldStride*offset, ~std::size_t{}}, - size, field._fieldStride}; + + field._field.data.stride*offset, ~std::size_t{}}, + size, field._field.data.stride}; } Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field) const { @@ -894,7 +1005,11 @@ SceneFieldData SceneData::fieldData(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldData(): index" << id << "out of range for" << _fields.size() << "fields", SceneFieldData{}); const SceneFieldData& field = _fields[id]; - return SceneFieldData{field._name, field._mappingType, fieldDataMappingViewInternal(field), field._fieldType, fieldDataFieldViewInternal(field), field._fieldArraySize, field._flags & ~SceneFieldFlag::OffsetOnly}; + 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 + return SceneFieldData{field._name, field.mappingType(), fieldDataMappingViewInternal(field), field._field.data.type, fieldDataFieldViewInternal(field), field._field.data.arraySize, flags}; } SceneField SceneData::fieldName(const UnsignedInt id) const { @@ -912,7 +1027,7 @@ SceneFieldFlags SceneData::fieldFlags(const UnsignedInt id) const { SceneFieldType SceneData::fieldType(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldType(): index" << id << "out of range for" << _fields.size() << "fields", {}); - return _fields[id]._fieldType; + return _fields[id].fieldType(); } std::size_t SceneData::fieldSize(const UnsignedInt id) const { @@ -924,7 +1039,7 @@ std::size_t SceneData::fieldSize(const UnsignedInt id) const { UnsignedShort SceneData::fieldArraySize(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldArraySize(): index" << id << "out of range for" << _fields.size() << "fields", {}); - return _fields[id]._fieldArraySize; + return _fields[id].fieldArraySize(); } UnsignedInt SceneData::findFieldIdInternal(const SceneField name) const { @@ -985,13 +1100,14 @@ template std::size_t findObject(const SceneFieldFlags flags, const Cont std::size_t SceneData::findFieldObjectOffsetInternal(const SceneFieldData& field, const UnsignedLong object, const std::size_t offset) const { const Containers::StridedArrayView1D mapping = fieldDataMappingViewInternal(field, offset, field._size - offset); - if(field._mappingType == SceneMappingType::UnsignedInt) + const SceneMappingType mappingType = field.mappingType(); + if(mappingType == SceneMappingType::UnsignedInt) return offset + findObject(field._flags, mapping, offset, object); - else if(field._mappingType == SceneMappingType::UnsignedShort) + else if(mappingType == SceneMappingType::UnsignedShort) return offset + findObject(field._flags, mapping, offset, object); - else if(field._mappingType == SceneMappingType::UnsignedByte) + else if(mappingType == SceneMappingType::UnsignedByte) return offset + findObject(field._flags, mapping, offset, object); - else if(field._mappingType == SceneMappingType::UnsignedLong) + else if(mappingType == SceneMappingType::UnsignedLong) return offset + findObject(field._flags, mapping, offset, object); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1091,7 +1207,7 @@ SceneFieldFlags SceneData::fieldFlags(const SceneField name) const { SceneFieldType SceneData::fieldType(const SceneField name) const { const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldType(): field" << name << "not found", {}); - return _fields[fieldId]._fieldType; + return _fields[fieldId].fieldType(); } std::size_t SceneData::fieldSize(const SceneField name) const { @@ -1103,7 +1219,7 @@ std::size_t SceneData::fieldSize(const SceneField name) const { UnsignedShort SceneData::fieldArraySize(const SceneField name) const { const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldArraySize(): field" << name << "not found", {}); - return _fields[fieldId]._fieldArraySize; + return _fields[fieldId].fieldArraySize(); } Containers::StridedArrayView2D SceneData::mapping(const UnsignedInt fieldId) const { @@ -1113,7 +1229,7 @@ Containers::StridedArrayView2D SceneData::mapping(const UnsignedInt /* Build a 2D view using information about mapping type size */ return Containers::arrayCast<2, const char>( fieldDataMappingViewInternal(field), - sceneMappingTypeSize(field._mappingType)); + sceneMappingTypeSize(field.mappingType())); } Containers::StridedArrayView2D SceneData::mutableMapping(const UnsignedInt fieldId) { @@ -1125,7 +1241,7 @@ Containers::StridedArrayView2D SceneData::mutableMapping(const UnsignedInt /* Build a 2D view using information about attribute type size */ const auto out = Containers::arrayCast<2, const char>( fieldDataMappingViewInternal(field), - sceneMappingTypeSize(field._mappingType)); + sceneMappingTypeSize(field.mappingType())); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -1152,10 +1268,11 @@ 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]; + const UnsignedShort fieldArraySize = field.fieldArraySize(); /* Build a 2D view using information about mapping type size */ return Containers::arrayCast<2, const char>( fieldDataFieldViewInternal(field), - sceneFieldTypeSize(field._fieldType)*(field._fieldArraySize ? field._fieldArraySize : 1)); + sceneFieldTypeSize(field.fieldType())*(fieldArraySize ? fieldArraySize : 1)); } Containers::StridedArrayView2D SceneData::mutableField(const UnsignedInt id) { @@ -1164,10 +1281,11 @@ 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]; + const UnsignedShort fieldArraySize = field.fieldArraySize(); /* Build a 2D view using information about attribute type size */ const auto out = Containers::arrayCast<2, const char>( fieldDataFieldViewInternal(field), - sceneFieldTypeSize(field._fieldType)*(field._fieldArraySize ? field._fieldArraySize : 1)); + sceneFieldTypeSize(field.fieldType())*(fieldArraySize ? fieldArraySize : 1)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -1192,6 +1310,123 @@ Containers::StridedArrayView2D SceneData::mutableField(const SceneField na return mutableField(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); +} + +const char* SceneData::fieldStringData(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldStringData(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field._mappingTypeStringType & Implementation::SceneMappingStringTypeMask, + "Trade::SceneData::fieldStringData():" << field._name << "is" << field._field.data.type << Debug::nospace << ", not a string", {}); + return fieldDataStringDataInternal(field); +} + +const char* SceneData::fieldStringData(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::fieldStringData(): field" << name << "not found", {}); + return fieldStringData(fieldId); +} + +namespace { + +/* These two take the `SceneFieldData` as a context pointer, instead of + `const char*`, in order to special-case the first element */ +template Containers::StringView fieldStringsAccessorOffset(const void* const data, const void* const context, const std::ptrdiff_t stride, const std::size_t i) { + const std::size_t currentOffset = *static_cast(data); + const std::size_t prevOffset = i == 0 ? 0 : *reinterpret_cast(static_cast(data) - stride); + return Containers::StringView{static_cast(context) + prevOffset, currentOffset - prevOffset}; +} +template Containers::StringView fieldStringsAccessorNullTerminatedOffset(const void* const data, const void* const context, const std::ptrdiff_t stride, const std::size_t i) { + const std::size_t currentOffset = *static_cast(data); + const std::size_t prevOffset = i == 0 ? 0 : *reinterpret_cast(static_cast(data) - stride); + return Containers::StringView{static_cast(context) + prevOffset, currentOffset - prevOffset - 1, Containers::StringViewFlag::NullTerminated}; +} +template Containers::StringView fieldStringsAccessorRange(const void* const data, const void* const context, std::ptrdiff_t, std::size_t) { + const auto& dataI = *static_cast*>(data); + return Containers::StringView{static_cast(context) + dataI.first(), std::size_t(dataI.second())}; +} +/* The difference between the two is that the first is for + SceneFieldType::StringRange* + SceneFieldFlag::NullTerminatedString, while + the second for SceneFieldType::StringRange*NullTerminated */ +template Containers::StringView fieldStringsAccessorNullTerminatedRange(const void* data, const void* context, std::ptrdiff_t, std::size_t) { + const auto& dataI = *static_cast*>(data); + return Containers::StringView{static_cast(context) + dataI.first(), std::size_t(dataI.second()), Containers::StringViewFlag::NullTerminated}; +} +template Containers::StringView fieldStringsAccessorRangeNullTerminated(const void* data, const void* context, std::ptrdiff_t, std::size_t) { + const auto& dataI = *static_cast(data); + /* StringViewFlag::NullTerminated is added implicitly, as the function + uses strlen() to calculate the size */ + return Containers::StringView{static_cast(context) + dataI}; +} + +} + +Containers::StringIterable SceneData::fieldStrings(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldStrings(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + CORRADE_ASSERT(field._mappingTypeStringType & Implementation::SceneMappingStringTypeMask, + "Trade::SceneData::fieldStrings():" << field._name << "is" << field._field.data.type << Debug::nospace << ", not a string", {}); + + /* Decide on the accessor callback */ + const SceneFieldType type = field.fieldType(); + Containers::StringView(*accessor)(const void*, const void*, std::ptrdiff_t, std::size_t); + if(type == SceneFieldType::StringOffset8) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedOffset : + fieldStringsAccessorOffset; + else if(type == SceneFieldType::StringOffset16) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedOffset : + fieldStringsAccessorOffset; + else if(type == SceneFieldType::StringOffset32) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedOffset : + fieldStringsAccessorOffset; + else if(type == SceneFieldType::StringOffset64) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedOffset : + fieldStringsAccessorOffset; + else if(type == SceneFieldType::StringRange8) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedRange : + fieldStringsAccessorRange; + else if(type == SceneFieldType::StringRange16) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedRange : + fieldStringsAccessorRange; + else if(type == SceneFieldType::StringRange32) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedRange : + fieldStringsAccessorRange; + else if(type == SceneFieldType::StringRange64) + accessor = field._flags & SceneFieldFlag::NullTerminatedString ? + fieldStringsAccessorNullTerminatedRange : + fieldStringsAccessorRange; + else if(type == SceneFieldType::StringRangeNullTerminated8) + accessor = fieldStringsAccessorRangeNullTerminated; + else if(type == SceneFieldType::StringRangeNullTerminated16) + accessor = fieldStringsAccessorRangeNullTerminated; + else if(type == SceneFieldType::StringRangeNullTerminated32) + accessor = fieldStringsAccessorRangeNullTerminated; + else if(type == SceneFieldType::StringRangeNullTerminated64) + accessor = fieldStringsAccessorRangeNullTerminated; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + const Containers::StridedArrayView1D data = fieldDataFieldViewInternal(field); + return Containers::StringIterable{data.data(), fieldDataStringDataInternal(field), data.size(), data.stride(), accessor}; +} + +Containers::StringIterable SceneData::fieldStrings(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::fieldStrings(): field" << name << "not found", {}); + return fieldStrings(fieldId); +} + void SceneData::mappingIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { /* fieldId, offset and destination.size() is assumed to be in bounds, checked by the callers */ @@ -1199,14 +1434,15 @@ void SceneData::mappingIntoInternal(const UnsignedInt fieldId, const std::size_t const SceneFieldData& field = _fields[fieldId]; const Containers::StridedArrayView1D mappingData = fieldDataMappingViewInternal(field, offset, destination.size()); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + const SceneMappingType mappingType = field.mappingType(); - if(field._mappingType == SceneMappingType::UnsignedInt) + if(mappingType == SceneMappingType::UnsignedInt) Utility::copy(Containers::arrayCast(mappingData), destination); - else if(field._mappingType == SceneMappingType::UnsignedShort) + else if(mappingType == SceneMappingType::UnsignedShort) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(mappingData, 1), destination1ui); - else if(field._mappingType == SceneMappingType::UnsignedByte) + else if(mappingType == SceneMappingType::UnsignedByte) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(mappingData, 1), destination1ui); - else if(field._mappingType == SceneMappingType::UnsignedLong) { + else if(mappingType == SceneMappingType::UnsignedLong) { Math::castInto(Containers::arrayCast<2, const UnsignedLong>(mappingData, 1), destination1ui); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1270,13 +1506,13 @@ void SceneData::parentsIntoInternal(const UnsignedInt fieldId, const std::size_t const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1i = Containers::arrayCast<2, Int>(destination); - if(field._fieldType == SceneFieldType::Int) + if(field._field.data.type == SceneFieldType::Int) Utility::copy(Containers::arrayCast(fieldData), destination); - else if(field._fieldType == SceneFieldType::Short) + else if(field._field.data.type == SceneFieldType::Short) Math::castInto(Containers::arrayCast<2, const Short>(fieldData, 1), destination1i); - else if(field._fieldType == SceneFieldType::Byte) + else if(field._field.data.type == SceneFieldType::Byte) Math::castInto(Containers::arrayCast<2, const Byte>(fieldData, 1), destination1i); - else if(field._fieldType == SceneFieldType::Long) { + else if(field._field.data.type == SceneFieldType::Long) { Math::castInto(Containers::arrayCast<2, const Long>(fieldData, 1), destination1i); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1435,17 +1671,17 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1f = Containers::arrayCast<2, Float>(destination); - if(field._fieldType == SceneFieldType::Matrix3x3) { + if(field._field.data.type == SceneFieldType::Matrix3x3) { Utility::copy(Containers::arrayCast(fieldData), destination); - } else if(field._fieldType == SceneFieldType::Matrix3x3d) { + } else if(field._field.data.type == SceneFieldType::Matrix3x3d) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 9), destination1f); - } else if(field._fieldType == SceneFieldType::Matrix3x2) { + } else if(field._field.data.type == SceneFieldType::Matrix3x2) { expandTransformationMatrix(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Matrix3x2d) { + } else if(field._field.data.type == SceneFieldType::Matrix3x2d) { expandTransformationMatrix(fieldData, destination); - } else if(field._fieldType == SceneFieldType::DualComplex) { + } else if(field._field.data.type == SceneFieldType::DualComplex) { convertTransformation(fieldData, destination); - } else if(field._fieldType == SceneFieldType::DualComplexd) { + } else if(field._field.data.type == SceneFieldType::DualComplexd) { convertTransformation(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ @@ -1460,9 +1696,9 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi const SceneFieldData& field = _fields[scalingFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); - if(field._fieldType == SceneFieldType::Vector2) { + if(field._field.data.type == SceneFieldType::Vector2) { applyScaling(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Vector2d) { + } else if(field._field.data.type == SceneFieldType::Vector2d) { applyScaling(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1472,9 +1708,9 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi const SceneFieldData& field = _fields[rotationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); - if(field._fieldType == SceneFieldType::Complex) { + if(field._field.data.type == SceneFieldType::Complex) { applyRotation(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Complexd) { + } else if(field._field.data.type == SceneFieldType::Complexd) { applyRotation(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1484,9 +1720,9 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi const SceneFieldData& field = _fields[translationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); - if(field._fieldType == SceneFieldType::Vector2) { + if(field._field.data.type == SceneFieldType::Vector2) { applyTranslation(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Vector2d) { + } else if(field._field.data.type == SceneFieldType::Vector2d) { applyTranslation(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1556,9 +1792,9 @@ void SceneData::translationsRotationsScalings2DIntoInternal(const UnsignedInt tr const SceneFieldData& field = _fields[translationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, translationDestination.size()); - if(field._fieldType == SceneFieldType::Vector2) { + if(field._field.data.type == SceneFieldType::Vector2) { Utility::copy(Containers::arrayCast(fieldData), translationDestination); - } else if(field._fieldType == SceneFieldType::Vector2d) { + } else if(field._field.data.type == SceneFieldType::Vector2d) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(translationDestination)); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1574,9 +1810,9 @@ void SceneData::translationsRotationsScalings2DIntoInternal(const UnsignedInt tr const SceneFieldData& field = _fields[rotationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, rotationDestination.size()); - if(field._fieldType == SceneFieldType::Complex) { + if(field._field.data.type == SceneFieldType::Complex) { Utility::copy(Containers::arrayCast(fieldData), rotationDestination); - } else if(field._fieldType == SceneFieldType::Complexd) { + } else if(field._field.data.type == SceneFieldType::Complexd) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(rotationDestination)); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1592,9 +1828,9 @@ void SceneData::translationsRotationsScalings2DIntoInternal(const UnsignedInt tr const SceneFieldData& field = _fields[scalingFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, scalingDestination.size()); - if(field._fieldType == SceneFieldType::Vector2) { + if(field._field.data.type == SceneFieldType::Vector2) { Utility::copy(Containers::arrayCast(fieldData), scalingDestination); - } else if(field._fieldType == SceneFieldType::Vector2d) { + } else if(field._field.data.type == SceneFieldType::Vector2d) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(scalingDestination)); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1681,17 +1917,17 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1f = Containers::arrayCast<2, Float>(destination); - if(field._fieldType == SceneFieldType::Matrix4x4) { + if(field._field.data.type == SceneFieldType::Matrix4x4) { Utility::copy(Containers::arrayCast(fieldData), destination); - } else if(field._fieldType == SceneFieldType::Matrix4x4d) { + } else if(field._field.data.type == SceneFieldType::Matrix4x4d) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 16), destination1f); - } else if(field._fieldType == SceneFieldType::Matrix4x3) { + } else if(field._field.data.type == SceneFieldType::Matrix4x3) { expandTransformationMatrix(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Matrix4x3d) { + } else if(field._field.data.type == SceneFieldType::Matrix4x3d) { expandTransformationMatrix(fieldData, destination); - } else if(field._fieldType == SceneFieldType::DualQuaternion) { + } else if(field._field.data.type == SceneFieldType::DualQuaternion) { convertTransformation(fieldData, destination); - } else if(field._fieldType == SceneFieldType::DualQuaterniond) { + } else if(field._field.data.type == SceneFieldType::DualQuaterniond) { convertTransformation(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ @@ -1706,9 +1942,9 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi const SceneFieldData& field = _fields[scalingFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); - if(field._fieldType == SceneFieldType::Vector3) { + if(field._field.data.type == SceneFieldType::Vector3) { applyScaling(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Vector3d) { + } else if(field._field.data.type == SceneFieldType::Vector3d) { applyScaling(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1718,9 +1954,9 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi const SceneFieldData& field = _fields[rotationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); - if(field._fieldType == SceneFieldType::Quaternion) { + if(field._field.data.type == SceneFieldType::Quaternion) { applyRotation(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Quaterniond) { + } else if(field._field.data.type == SceneFieldType::Quaterniond) { applyRotation(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1730,9 +1966,9 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi const SceneFieldData& field = _fields[translationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); - if(field._fieldType == SceneFieldType::Vector3) { + if(field._field.data.type == SceneFieldType::Vector3) { applyTranslation(fieldData, destination); - } else if(field._fieldType == SceneFieldType::Vector3d) { + } else if(field._field.data.type == SceneFieldType::Vector3d) { applyTranslation(fieldData, destination); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1802,9 +2038,9 @@ void SceneData::translationsRotationsScalings3DIntoInternal(const UnsignedInt tr const SceneFieldData& field = _fields[translationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, translationDestination.size()); - if(field._fieldType == SceneFieldType::Vector3) { + if(field._field.data.type == SceneFieldType::Vector3) { Utility::copy(Containers::arrayCast(fieldData), translationDestination); - } else if(field._fieldType == SceneFieldType::Vector3d) { + } else if(field._field.data.type == SceneFieldType::Vector3d) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 3), Containers::arrayCast<2, Float>(translationDestination)); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1820,9 +2056,9 @@ void SceneData::translationsRotationsScalings3DIntoInternal(const UnsignedInt tr const SceneFieldData& field = _fields[rotationFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, rotationDestination.size()); - if(field._fieldType == SceneFieldType::Quaternion) { + if(field._field.data.type == SceneFieldType::Quaternion) { Utility::copy(Containers::arrayCast(fieldData), rotationDestination); - } else if(field._fieldType == SceneFieldType::Quaterniond) { + } else if(field._field.data.type == SceneFieldType::Quaterniond) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 4), Containers::arrayCast<2, Float>(rotationDestination)); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1838,9 +2074,9 @@ void SceneData::translationsRotationsScalings3DIntoInternal(const UnsignedInt tr const SceneFieldData& field = _fields[scalingFieldId]; const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, scalingDestination.size()); - if(field._fieldType == SceneFieldType::Vector3) { + if(field._field.data.type == SceneFieldType::Vector3) { Utility::copy(Containers::arrayCast(fieldData), scalingDestination); - } else if(field._fieldType == SceneFieldType::Vector3d) { + } else if(field._field.data.type == SceneFieldType::Vector3d) { Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 3), Containers::arrayCast<2, Float>(scalingDestination)); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1919,11 +2155,11 @@ void SceneData::unsignedIndexFieldIntoInternal(const UnsignedInt fieldId, const const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); - if(field._fieldType == SceneFieldType::UnsignedInt) + if(field._field.data.type == SceneFieldType::UnsignedInt) Utility::copy(Containers::arrayCast(fieldData), destination); - else if(field._fieldType == SceneFieldType::UnsignedShort) + else if(field._field.data.type == SceneFieldType::UnsignedShort) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(fieldData, 1), destination1ui); - else if(field._fieldType == SceneFieldType::UnsignedByte) + else if(field._field.data.type == SceneFieldType::UnsignedByte) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(fieldData, 1), destination1ui); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -1936,11 +2172,11 @@ void SceneData::indexFieldIntoInternal(const UnsignedInt fieldId, const std::siz const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1ui = Containers::arrayCast<2, Int>(destination); - if(field._fieldType == SceneFieldType::Int) + if(field._field.data.type == SceneFieldType::Int) Utility::copy(Containers::arrayCast(fieldData), destination); - else if(field._fieldType == SceneFieldType::Short) + else if(field._field.data.type == SceneFieldType::Short) Math::castInto(Containers::arrayCast<2, const Short>(fieldData, 1), destination1ui); - else if(field._fieldType == SceneFieldType::Byte) + else if(field._field.data.type == SceneFieldType::Byte) Math::castInto(Containers::arrayCast<2, const Byte>(fieldData, 1), destination1ui); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -2136,8 +2372,8 @@ void SceneData::importerStateIntoInternal(const UnsignedInt fieldId, const std:: checked by the callers */ const SceneFieldData& field = _fields[fieldId]; - CORRADE_INTERNAL_ASSERT(field._fieldType == SceneFieldType::Pointer || - field._fieldType == SceneFieldType::MutablePointer); + CORRADE_INTERNAL_ASSERT(field._field.data.type == SceneFieldType::Pointer || + field._field.data.type == SceneFieldType::MutablePointer); Utility::copy(Containers::arrayCast(fieldDataFieldViewInternal(field, offset, destination.size())), destination); } diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index dfb674a45..7f29d9e68 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -61,8 +61,20 @@ enum class SceneMappingType: UnsignedByte { UnsignedShort, /**< @relativeref{Magnum,UnsignedShort} */ UnsignedInt, /**< @relativeref{Magnum,UnsignedInt} */ UnsignedLong /**< @relativeref{Magnum,UnsignedLong} */ + + /* Bits 3-6 used to store `SceneFieldType::String* << 3` in + SceneFieldData::_mappingTypeStringType */ }; +namespace Implementation { + +enum: UnsignedByte { + SceneMappingTypeMask = 0x07, /* covers SceneMappingType values */ + SceneMappingStringTypeMask = 0xf8 /* covers `SceneFieldType::String* << 3` */ +}; + +} + /** @debugoperatorenum{SceneMappingType} @m_since_latest @@ -382,9 +394,12 @@ information. enum class SceneFieldType: UnsignedShort { /* Zero used for an invalid value */ - /* 1 reserved for Bool (Bit?), which needs [Strided]BitArray[View] first */ + /* 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 */ - Float = 2, /**< @relativeref{Magnum,Float} */ + /* 13 reserved for Bit, which needs StridedBitArrayView first */ + + Float = 14, /**< @relativeref{Magnum,Float} */ Half, /**< @relativeref{Magnum,Half} */ Double, /**< @relativeref{Magnum,Double} */ UnsignedByte, /**< @relativeref{Magnum,UnsignedByte} */ @@ -508,6 +523,156 @@ enum class SceneFieldType: UnsignedShort { * an arbitrary `T` but the user has to ensure the type is correct. */ MutablePointer, + + /* String types defined at the end because of exquisite complexity */ + + /** + * 32-bit string offsets with implicit sizes. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * Internally, the first string starts at @ref SceneData::fieldStringData(), + * second string starts at @relativeref{SceneData,fieldStringData()} plus + * @ref SceneData::field() "field()[0]", etc. String sizes are + * implicitly the distance between two successive offsets or + * @cpp field()[0] @ce in case of the first string; if + * @ref SceneFieldFlag::NullTerminatedString is set the distance includes + * the null terminator. + * + * The `StringOffset*` types are useful mainly for cases where each string + * is unique, for strings with many duplicates + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*" or + * @ref SceneFieldType::StringRangeNullTerminated32 "StringRangeNullTerminated32*" + * may be a more space-efficient option. + */ + StringOffset32 = 3, + + /** + * 8-bit string offsets with implicit sizes. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to @ref SceneFieldType::StringOffset32 + * except that @relativeref{Magnum,UnsignedByte} is used as the type, see + * its documentation for more information. + */ + StringOffset8 = 1, + + /** + * 16-bit string offsets with implicit sizes. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to @ref SceneFieldType::StringOffset32 + * except that @relativeref{Magnum,UnsignedShort} is used as the type, see + * its documentation for more information. + */ + StringOffset16 = 2, + + /** + * 64-bit string offsets with implicit sizes. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to @ref SceneFieldType::StringOffset32 + * except that @relativeref{Magnum,UnsignedLong} is used as the type, see + * its documentation for more information. + */ + StringOffset64 = 4, + + /** + * @relativeref{Corrade,Containers::Pair} of 32-bit string offsets and + * sizes. Use @ref SceneData::fieldStrings() for convenient access. + * + * Internally, string `i` starts at @ref SceneData::fieldStringData() plus + * @ref SceneData::field() "field>()[i].first()" + * and has a size of @cpp field>()[i].second() @ce. + * + * The main use case for `StringRange*` types is to be able to reference + * the same string from multiple field entries without having to duplicate + * it. For strings without duplicates + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*" may + * be a more space-efficient option, as the size is implicit. + * Alternatively, @ref SceneFieldType::StringRangeNullTerminated32 "StringRangeNullTerminated32*" + * has the same space requirements as + * @ref SceneFieldType::StringOffset32 "StringOffset*" with + * @ref SceneFieldFlag::NullTerminatedString set and allows deduplication, + * however at the cost of a @ref std::strlen() call on every access. + */ + StringRange32 = 7, + + /** + * @relativeref{Corrade,Containers::Pair} of 8-bit string offsets and + * sizes. Use @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to @ref SceneFieldType::StringRange32 + * except that @relativeref{Magnum,UnsignedByte} is used for the pair + * types, see its documentation for more information. + */ + StringRange8 = 5, + + /** + * @relativeref{Corrade,Containers::Pair} of 16-bit string offsets and + * sizes. Use @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to @ref SceneFieldType::StringRange32 + * except that @relativeref{Magnum,UnsignedShort} is used for the pair + * types, see its documentation for more information. + */ + StringRange16 = 6, + + /** + * @relativeref{Corrade,Containers::Pair} of 64-bit string offsets and + * sizes. Use @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to @ref SceneFieldType::StringRange32 + * except that @relativeref{Magnum,UnsignedLong} is used for the pair + * types, see its documentation for more information. + */ + StringRange64 = 8, + + /** + * 32-bit offsets to null-terminated strings. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * Compared to @ref SceneFieldType::StringRange32 stores just the offset, + * the size is calculated on-the-fly with @ref std::strlen(). + * + * Internally, string `i` starts at @ref SceneData::fieldStringData() plus + * @ref SceneData::field() "field()[i]", size is implicitly + * until the first @cpp '\0' @ce byte. See + * @ref SceneFieldType::StringRange32 for use case recommendations. + */ + StringRangeNullTerminated32 = 11, + + /** + * 8-bit offsets to null-terminated strings. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to + * @ref SceneFieldType::StringRangeNullTerminated32 except that + * @relativeref{Magnum,UnsignedByte} is used as the type, see its + * documentation for more information. + */ + StringRangeNullTerminated8 = 9, + + /** + * 16-bit offsets to null-terminated strings. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to + * @ref SceneFieldType::StringRangeNullTerminated32 except that + * @relativeref{Magnum,UnsignedShort} is used as the type, see its + * documentation for more information. + */ + StringRangeNullTerminated16 = 10, + + /** + * 32-bit offsets to null-terminated strings. Use + * @ref SceneData::fieldStrings() for convenient access. + * + * The internal layout is similar to + * @ref SceneFieldType::StringRangeNullTerminated32 except that + * @relativeref{Magnum,UnsignedLong} is used as the type, see its + * documentation for more information. + */ + StringRangeNullTerminated64 = 12, }; /** @@ -582,6 +747,22 @@ enum class SceneFieldFlag: UnsignedByte { * @f$ \mathcal{O}(n) @f$ lookup complexity. */ ImplicitMapping = (1 << 2)|OrderedMapping, + + /** + * The string field is null-terminated, i.e. string views returned from + * @ref SceneData::fieldStrings() will always have + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set. + * Internally it means that the distance between successive + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*" + * entries includes the null terminator; + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*" size + * is excluding the null terminator but assumes it's present right after; + * for @ref SceneFieldType::StringRangeNullTerminated32 "SceneFieldType,StringRangeNullTerminated*" + * it's set implicitly as that's the default behavior. + * + * Can only be set for string @ref SceneFieldType. + */ + NullTerminatedString = 1 << 3 }; /** @@ -610,8 +791,8 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldFlags value); @brief Scene field data @m_since_latest -Convenience type for populating @ref SceneData, see its documentation for an -introduction. +Convenience type for populating @ref SceneData, see +@ref Trade-SceneData-populating "its documentation" for an introduction. @section Trade-SceneFieldData-usage Usage @@ -653,6 +834,20 @@ field specifying data for object @cpp 0 @ce, second entry for object @cpp 1 @ce, third for object @cpp 2 @ce and so on. You can annotate such fields with @ref SceneFieldFlag::ImplicitMapping, which is a superset of @relativeref{SceneFieldFlag,OrderedMapping}. + +@subsection Trade-SceneFieldData-usage-strings String fields + +String fields have to be constructed using dedicated constructors that +additionally take a @cpp const char* @ce base string pointer, and because a +particular type can correspond to more than one @ref SceneFieldType (such as +@ref SceneFieldType::StringOffset32 and +@ref SceneFieldType::StringRangeNullTerminated32 being both represented with an +@relativeref{Magnum,UnsignedInt}), the type has to be specified explicitly: + +@snippet MagnumTrade.cpp SceneFieldData-usage-strings + +Offset-only constructors have it similar, containing an extra base string +offset. Due to packing in the internal layout, string fields can't be arrays. */ class MAGNUM_TRADE_EXPORT SceneFieldData { public: @@ -663,7 +858,7 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * initialization of the field array for @ref SceneData, expected to be * replaced with concrete values later. */ - constexpr explicit SceneFieldData() noexcept: _size{}, _name{}, _flags{}, _mappingType{}, _mappingStride{}, _mappingData{}, _fieldStride{}, _fieldType{}, _fieldArraySize{}, _fieldData{} {} + constexpr explicit SceneFieldData() noexcept: _size{}, _name{}, _flags{}, _mappingTypeStringType{}, _mappingStride{}, _mappingData{}, _field{}, _fieldData{} {} /** * @brief Construct from type-erased views @@ -675,7 +870,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. * @param flags Field flags. - * @ref SceneFieldFlag::OffsetOnly is not allowed here. + * @ref SceneFieldFlag::OffsetOnly and + * @ref SceneFieldFlag::NullTerminatedString is not allowed here. * * Expects that @p mappingData and @p fieldData have the same size; and * for builtin fields that @p fieldType corresponds to @p name and @@ -695,7 +891,8 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param fieldArraySize Field array size. Use @cpp 0 @ce for * non-array fields. * @param flags Field flags. - * @ref SceneFieldFlag::OffsetOnly is not allowed here. + * @ref SceneFieldFlag::OffsetOnly and + * @ref SceneFieldFlag::NullTerminatedString is not allowed here. * * Expects that @p mappingData and @p fieldData have the same size in * the first dimension; that the second dimension of @p mappingData is @@ -715,8 +912,9 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param name Field name * @param mappingData Object mapping data * @param fieldData Field data - * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is - * not allowed here. + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. * * Detects @ref SceneMappingType based on @p T and @ref SceneFieldType * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags). @@ -740,8 +938,9 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * @param name Field name * @param mappingData Object mapping data * @param fieldData Field data - * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is - * not allowed here. + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly + * and @ref SceneFieldFlag::NullTerminatedString is not allowed + * here. * * Detects @ref SceneMappingType based on @p T and @ref SceneFieldType * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags) @@ -758,6 +957,87 @@ 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 string field from type-erased views + * @param name Field name + * @param mappingType Object mapping type + * @param mappingData Object mapping data + * @param stringData String to which the field offset or range data + * are relative to + * @param fieldType Field type. Only `SceneFieldType::String*` + * values are allowed here. + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. @ref SceneFieldFlag::NullTerminatedString is + * set implicitly for + * @ref SceneFieldType::StringRangeNullTerminated8, + * @relativeref{SceneFieldType,StringRangeNullTerminated16}, + * @relativeref{SceneFieldType,StringRangeNullTerminated32} and + * @relativeref{SceneFieldType,StringRangeNullTerminated64}. + * + * Expects that @p mappingData and @p fieldData have the same size, + * @p fieldType corresponds to @p name and @p fieldArraySize is zero + * for builtin fields. + */ + /* While a StringView could provide the range assertions with more + context inside the SceneFieldData constructor, the main range + checking happens in the SceneField constructor, at which point the + size would be gone anyway as SceneFieldData can store only the begin + pointer inside. Using it would also mean we'd need to include its + full definition in this header. */ + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const char* stringData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** + * @brief Construct a string field from 2D views + * @param name Field name + * @param mappingData Object mapping data + * @param stringData String to which the field offset or range data + * are relative to + * @param fieldType Field type. Only `SceneFieldType::String*` + * values are allowed here. + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. @ref SceneFieldFlag::NullTerminatedString is + * set implicitly for + * @ref SceneFieldType::StringRangeNullTerminated8, + * @relativeref{SceneFieldType,StringRangeNullTerminated16}, + * @relativeref{SceneFieldType,StringRangeNullTerminated32} and + * @relativeref{SceneFieldType,StringRangeNullTerminated64}. + * + * 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; that the second dimension of + * @p fieldData is contiguous and its size matches @p fieldType and + * @p fieldArraySize; and that for builtin fields @p fieldType + * corresponds to @p name and @p fieldArraySize is zero. + */ + /* See above for why const char* is used instead of StringView */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, const char* stringData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** + * @brief Construct a string field + * @param name Field name + * @param mappingData Object mapping data + * @param stringData String to which the field offset or range data + * are relative to + * @param fieldType Field type. Only `SceneFieldType::String*` + * values are allowed here. + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. @ref SceneFieldFlag::NullTerminatedString is + * set implicitly for + * @ref SceneFieldType::StringRangeNullTerminated8, + * @relativeref{SceneFieldType,StringRangeNullTerminated16}, + * @relativeref{SceneFieldType,StringRangeNullTerminated32} and + * @relativeref{SceneFieldType,StringRangeNullTerminated64}. + */ + /* See above for why const char* is used instead of StringView */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const char* stringData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const char* stringData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), stringData, fieldType, fieldData, flags} {} + /** * @brief Construct an offset-only field * @param name Field name @@ -772,6 +1052,7 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { * 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. @@ -793,6 +1074,44 @@ 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 string 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 stringOffset String data offset to which the field + * offset or range data are relative to + * @param fieldType Field type. Only `SceneFieldType::String*` + * values are allowed here. + * @param fieldOffset Field data offset + * @param fieldStride Field data stride + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is set implicitly. + * @ref SceneFieldFlag::NullTerminatedString is set implicitly for + * @ref SceneFieldType::StringRangeNullTerminated8, + * @relativeref{SceneFieldType,StringRangeNullTerminated16}, + * @relativeref{SceneFieldType,StringRangeNullTerminated32} and + * @relativeref{SceneFieldType,StringRangeNullTerminated64}. + * + * 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. Expects that for builtin fields + * @p fieldType corresponds to @p name and @p fieldArraySize is zero. + * + * Note that due to the @cpp constexpr @ce nature of this constructor, + * no @p mappingType checks against @p mappingStride or + * @p fieldType checks against @p fieldStride can be done. You're + * encouraged to use the @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags) + * constructor 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 stringOffset, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, SceneFieldFlags flags = {}) noexcept; + /** @brief Field flags */ constexpr SceneFieldFlags flags() const { return _flags; } @@ -803,7 +1122,9 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { constexpr UnsignedLong size() const { return _size; } /** @brief Object mapping type */ - constexpr SceneMappingType mappingType() const { return _mappingType; } + constexpr SceneMappingType mappingType() const { + return SceneMappingType(_mappingTypeStringType & Implementation::SceneMappingTypeMask); + } /** * @brief Type-erased object mapping data @@ -825,10 +1146,16 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { Containers::StridedArrayView1D mappingData(Containers::ArrayView data) const; /** @brief Field type */ - constexpr SceneFieldType fieldType() const { return _fieldType; } + SceneFieldType fieldType() const { + return _mappingTypeStringType & Implementation::SceneMappingStringTypeMask ? + SceneFieldType((_mappingTypeStringType & Implementation::SceneMappingStringTypeMask) >> 3) : _field.data.type; + } /** @brief Field array size */ - constexpr UnsignedShort fieldArraySize() const { return _fieldArraySize; } + UnsignedShort fieldArraySize() const { + return _mappingTypeStringType & Implementation::SceneMappingStringTypeMask ? + 0 : _field.data.arraySize; + } /** * @brief Type-erased field data @@ -849,6 +1176,39 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { */ Containers::StridedArrayView1D fieldData(Containers::ArrayView data) const; + /** + * @brief Base data pointer for a string field + * + * Offsets and ranges returned from @ref fieldData() are relative to + * this pointer. Can be only called on + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "StringRange*" + * and @ref SceneFieldType::StringRangeNullTerminated32 "StringRangeNullTerminated*" + * fields. + * + * Expects that the field does not have @ref SceneFieldFlag::OffsetOnly + * set, in that case use the @ref stringData(Containers::ArrayView) const + * overload instead. + * @see @ref flags() + */ + const char* stringData() const; + + /** + * @brief Base data pointer for an offset-only string field + * + * Offsets and ranges returned from @ref SceneData::field() are + * relative to this pointer. Can be only called on + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "StringRange*" + * and @ref SceneFieldType::StringRangeNullTerminated32 "StringRangeNullTerminated*" + * fields. + * + * If the field does not have @ref SceneFieldFlag::OffsetOnly set, the + * @p data parameter is ignored. + * @see @ref flags(), @ref fieldData() const + */ + const char* stringData(Containers::ArrayView data) const; + private: friend SceneData; @@ -866,14 +1226,49 @@ class MAGNUM_TRADE_EXPORT SceneFieldData { UnsignedLong _size; SceneField _name; SceneFieldFlags _flags; - SceneMappingType _mappingType; + /* Contains SceneMappingType in the lower 3 bits. If the + next 4 bits are non-zero, they encode one of the + `SceneFieldType::String* << 3` values. 1 remaining bit unused. */ + UnsignedByte _mappingTypeStringType; Short _mappingStride; Data _mappingData; - Short _fieldStride; - SceneFieldType _fieldType; - UnsignedShort _fieldArraySize; - /* 2 bytes free */ + /* Contains either of the two following layouts depending on whether + _mappingTypeStringType marks this as a string field. In that case + the type is stored in _mappingTypeStringType already and the + remaining 6 bytes store a *signed* string data offset: + + - if SceneFieldFlag::OffsetOnly is not set, relative to + _fieldData.pointer + - if OffsetOnly is set, absolute + + The 6 bytes allow addressing up to ±128 TB of data, which should be + 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 +------------+------------+------------+ + | | string data offset | + +------------+--------------------------------------+ + 8 6 4 2 0 BE */ + union Field { + /* C++, if you wouldn't be stupid, these constructors wouldn't be + 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, Long offset): strideOffset{(UnsignedLong(stride) & 0xffff)|((UnsignedLong(offset) & 0xffffffffffffull) << 16)} {} + + struct { + Short stride; + SceneFieldType type; + UnsignedShort arraySize; + /* 2 bytes unused */ + } data; + UnsignedLong strideOffset; /* Upper 48 bits on LE, lower 48 on BE */ + } _field; Data _fieldData; }; @@ -1142,6 +1537,57 @@ The light references are actually a 2D array (8 lights for each cell), so a @snippet MagnumTrade.cpp SceneData-populating-custom-retrieve +@subsection Trade-SceneData-populating-strings String fields + +Besides fixed-size types and their arrays, the @ref SceneData class is capable +of storing strings. A string field consists of a base pointer and a list +containing offsets or ranges relative to that base pointer. Such separation +allows more efficient storage compared to storing a full pointer (or a pair of +a pointer and size) for every field, as it's possible to choose a smaller +offset type if the referenced strings fit into a 8- or 16-bit range. + +To cover different use cases, there's multiple ways how to store the string +references: + +- @ref SceneFieldType::StringOffset32 and its 8-, 16- and 64-bit variants + store a running offset into the string array. For example, offsets + @cpp {3, 11, 23} @ce would mean the first string is from byte 0 to 3, + second is from byte 3 to 11 and third from byte 11 to 23. This storage type + is most efficient if the strings are unique for each entry --- such as + various names or external data filenames. +- @ref SceneFieldType::StringRange32 and its 8-, 16- and 64-bit variants + store a pair of offset and size. For example, ranges + @cpp {{11, 5}, {4, 7}, {11, 5}} @ce would mean the first and third string + is from byte 11 to 16 and the second string is from byte 4 to 11. This + storage type is thus most efficient when there's many duplicates --- such + as various object tags or categories. +- @ref SceneFieldType::StringRangeNullTerminated32 and its 8-, 16- and 64-bit + variants are similar to @ref SceneFieldType::StringRange32, but stores just + the offset and size is implicitly until the next @cpp '\0' @ce byte. Thus + it trades a slightly better space efficiency for the cost of a runtime + @ref std::strlen() call on every access. + +String fields can also have @ref SceneFieldFlag::NullTerminatedString set, in +which case the returned @ref Containers::StringView instances will have +@relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set, which may +be useful for example to avoid an allocation when passing filenames to OS APIs +in @ref Utility::Path::read(). The null terminators of course have to be stored +in the data itself, see a particular @ref SceneFieldType for information about +how it affects the field encoding. + +The following example shows populating a @ref SceneData with a "tag" string +field, stored as null-terminated 8-bit string array ranges. In other words --- +assuming there's enough stored data --- the space efficiency is the same as if +a just a numeric value of an 8-bit @cpp enum @ce would be stored, but here it +includes human-readable string names. + +@snippet MagnumTrade.cpp SceneData-populating-strings + +While there's many options how to store the string, retrieving of any string +@ref SceneFieldType can be conveniently done using @ref fieldStrings(): + +@snippet MagnumTrade.cpp SceneData-populating-strings-retrieve + @see @ref AbstractImporter::scene() */ class MAGNUM_TRADE_EXPORT SceneData { @@ -1413,10 +1859,15 @@ class MAGNUM_TRADE_EXPORT SceneData { * In case given field is an array (the euqivalent of e.g. * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. * At the moment only custom fields can be arrays, no builtin - * @ref SceneField is an array field. Note that this is different from - * the count of entries for given field, which is exposed through - * @ref fieldSize(). See @ref Trade-SceneData-populating-custom for an - * example. + * @ref SceneField is an array field. Additionally, fields with + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*" or + * @ref SceneFieldType::StringRangeNullTerminated32 "SceneFieldType::StringRangeNullTerminated*" + * can't be arrays, for them the function always returns @cpp 0 @ce. + * + * Note that this is different from the count of entries for given + * field, which is exposed through @ref fieldSize(). See + * @ref Trade-SceneData-populating-custom for an example. * * The @p id is expected to be smaller than @ref fieldCount(). You can * also use @ref fieldArraySize(SceneField) const to directly get a @@ -1890,6 +2341,70 @@ class MAGNUM_TRADE_EXPORT SceneData { */ template::value>::type> Containers::StridedArrayView2D::type> mutableField(SceneField name); + /** + * @brief Base data pointer for given string field + * @m_since_latest + * + * Raw string offsets and ranges returned from @ref field() are + * relative to this pointer. For more convenient access use + * @ref fieldStrings() instead. Expects that @p id is smaller than + * @ref fieldCount() const and that the field is + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*" or + * @ref SceneFieldType::StringRangeNullTerminated32 "SceneFieldType::StringRangeNullTerminated*". + * You can also use @ref fieldStringData(SceneField) const to directly + * get a data pointer for given named string field. + * @see @ref fieldType(UnsignedInt) const + */ + const char* fieldStringData(UnsignedInt id) const; + + /** + * @brief Base data pointer for given named string field + * @m_since_latest + * + * Expects that @p name exists and is + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*" or + * @ref SceneFieldType::StringRangeNullTerminated32 "SceneFieldType::StringRangeNullTerminated*". See + * @ref fieldStringData(UnsignedInt) const for more information. + * @see @ref hasField() + */ + const char* fieldStringData(SceneField name) const; + + /** + * @brief Contents of given string field + * @m_since_latest + * + * The returned views point to strings owned by this instance. If the + * field has @ref SceneFieldFlag::NullTerminatedString set, the + * returned views all have @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * set. Expects that @p id is smaller than @ref fieldCount() const and + * that the field is + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*" or + * @ref SceneFieldType::StringRangeNullTerminated32 "SceneFieldType::StringRangeNullTerminated*". You can also use + * @ref fieldStrings(SceneField) const to directly get contents of + * given named string field. + * + * The raw string data can be accessed using @ref fieldStringData() and + * @ref field(). See a particular @ref SceneFieldType for information + * about how to interpret the data. + */ + Containers::StringIterable fieldStrings(UnsignedInt id) const; + + /** + * @brief Contents of given string field + * @m_since_latest + * + * Expects that @p name exists and that the field is + * @ref SceneFieldType::StringOffset32 "SceneFieldType::StringOffset*", + * @ref SceneFieldType::StringRange32 "SceneFieldType::StringRange*", + * @ref SceneFieldType::StringRangeNullTerminated32 "SceneFieldType::StringRangeNullTerminated*". See + * @ref fieldStrings(UnsignedInt) const for more information. + * @see @ref hasField() + */ + Containers::StringIterable fieldStrings(SceneField name) const; + /** * @brief Object mapping for given field as 32-bit integers * @m_since_latest @@ -2839,6 +3354,8 @@ class MAGNUM_TRADE_EXPORT SceneData { MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataMappingViewInternal(const SceneFieldData& field) const; MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataFieldViewInternal(const SceneFieldData& field, std::size_t offset, std::size_t size) const; MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataFieldViewInternal(const SceneFieldData& field) const; + /* Assertion-less helper for fieldStringData() and fieldStrings() */ + MAGNUM_TRADE_LOCAL const char* fieldDataStringDataInternal(const SceneFieldData& field) const; #ifndef CORRADE_NO_ASSERT template bool checkFieldTypeCompatibility(const SceneFieldData& field, const char* prefix) const; @@ -2876,11 +3393,12 @@ namespace Implementation { static_assert(sizeof(T) == 0, "unsupported field type"); }; #ifndef DOXYGEN_GENERATING_OUTPUT - #define _c(type_) template<> struct SceneFieldTypeFor { \ + #define _cn(name, ...) template<> struct SceneFieldTypeFor<__VA_ARGS__> { \ constexpr static SceneFieldType type() { \ - return SceneFieldType::type_; \ + return SceneFieldType::name; \ } \ }; + #define _c(type_) _cn(type_, type_) /* Bool needs a special type */ _c(Float) _c(Half) @@ -2973,7 +3491,12 @@ namespace Implementation { _c(Rad) _c(Radh) _c(Radd) + _cn(StringRange8, Containers::Pair) + _cn(StringRange16, Containers::Pair) + _cn(StringRange32, Containers::Pair) + _cn(StringRange64, Containers::Pair) #undef _c + #undef _cn #endif /** @todo this doesn't handle RectangleMatrix and Vector at the moment, do we need those? */ template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; @@ -2999,6 +3522,34 @@ namespace Implementation { return type == SceneFieldTypeFor::type(); } }; + template<> struct SceneFieldTypeTraits { + constexpr static bool isCompatible(SceneFieldType type) { + return type == SceneFieldType::UnsignedByte || + type == SceneFieldType::StringOffset8 || + type == SceneFieldType::StringRangeNullTerminated8; + } + }; + template<> struct SceneFieldTypeTraits { + constexpr static bool isCompatible(SceneFieldType type) { + return type == SceneFieldType::UnsignedShort || + type == SceneFieldType::StringOffset16 || + type == SceneFieldType::StringRangeNullTerminated16; + } + }; + template<> struct SceneFieldTypeTraits { + constexpr static bool isCompatible(SceneFieldType type) { + return type == SceneFieldType::UnsignedInt || + type == SceneFieldType::StringOffset32 || + type == SceneFieldType::StringRangeNullTerminated32; + } + }; + template<> struct SceneFieldTypeTraits { + constexpr static bool isCompatible(SceneFieldType type) { + return type == SceneFieldType::UnsignedLong || + type == SceneFieldType::StringOffset64 || + type == SceneFieldType::StringRangeNullTerminated64; + } + }; template constexpr SceneMappingType sceneMappingTypeFor() { static_assert(sizeof(T) == 0, "unsupported mapping type"); @@ -3067,6 +3618,31 @@ namespace Implementation { constexpr bool isSceneFieldArrayAllowed(SceneField name) { return isSceneFieldCustom(name); } + + constexpr bool isSceneFieldTypeString(SceneFieldType type) { + return + type == SceneFieldType::StringOffset8 || + type == SceneFieldType::StringOffset16 || + type == SceneFieldType::StringOffset32 || + type == SceneFieldType::StringOffset64 || + type == SceneFieldType::StringRange8 || + type == SceneFieldType::StringRange16 || + type == SceneFieldType::StringRange32 || + type == SceneFieldType::StringRange64 || + type == SceneFieldType::StringRangeNullTerminated8 || + type == SceneFieldType::StringRangeNullTerminated16 || + type == SceneFieldType::StringRangeNullTerminated32 || + type == SceneFieldType::StringRangeNullTerminated64; + } + + constexpr SceneFieldFlags implicitSceneFieldFlagsFor(SceneFieldType type) { + return + type == SceneFieldType::StringRangeNullTerminated8 || + type == SceneFieldType::StringRangeNullTerminated16 || + type == SceneFieldType::StringRangeNullTerminated32 || + type == SceneFieldType::StringRangeNullTerminated64 ? + SceneFieldFlag::NullTerminatedString : SceneFieldFlags{}; + } } constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: @@ -3074,17 +3650,19 @@ constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappi "Trade::SceneFieldData: expected" << name << "mapping and field view to have the same size but got" << mappingData.size() << "and" << fieldData.size()), mappingData.size())}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, - _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & SceneFieldFlag::OffsetOnly), - "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view"), flags)}, - _mappingType{mappingType}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & (SceneFieldFlag::OffsetOnly|SceneFieldFlag::NullTerminatedString)), + "Trade::SceneFieldData: can't pass" << (flags & (SceneFieldFlag::OffsetOnly|SceneFieldFlag::NullTerminatedString)) << "for a view of" << fieldType), flags)}, + _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()}, - _fieldStride{(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()))}, - _fieldType{fieldType}, - _fieldArraySize{(CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), - "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, + _field{ + (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), + (CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), + "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, _fieldData{fieldData.data()} {} template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, Implementation::SceneFieldTypeFor::type>::type(), fieldData, 0, flags} {} @@ -3100,20 +3678,59 @@ template constexpr SceneFieldData::SceneFieldData(const SceneF flags } {} +constexpr 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{(CORRADE_CONSTEXPR_ASSERT(mappingData.size() == fieldData.size(), + "Trade::SceneFieldData: expected" << name << "mapping and field view to have the same size but got" << mappingData.size() << "and" << fieldData.size()), mappingData.size())}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), + "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & SceneFieldFlag::OffsetOnly), + "Trade::SceneFieldData: can't pass" << (flags & SceneFieldFlag::OffsetOnly) << "for a view"), flags|Implementation::implicitSceneFieldFlagsFor(fieldType))}, + _mappingTypeStringType{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeString(fieldType), + "Trade::SceneFieldData: can't use a string constructor for" << fieldType), UnsignedByte(UnsignedByte(mappingType)|(UnsignedShort(fieldType) << 3)))}, + _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(fieldData.stride() >= -32768 && fieldData.stride() <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldData.stride()), Short(fieldData.stride())), + stringData - static_cast(fieldData.data())}, + _fieldData{fieldData.data()} {} + +template constexpr 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: _size{size}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, - _flags{flags|SceneFieldFlag::OffsetOnly}, - _mappingType{mappingType}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & SceneFieldFlag::NullTerminatedString), + "Trade::SceneFieldData: can't pass" << (flags & SceneFieldFlag::NullTerminatedString) << "for" << fieldType), flags|SceneFieldFlag::OffsetOnly)}, + _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)), + (CORRADE_CONSTEXPR_ASSERT(!Implementation::isSceneFieldTypeString(fieldType), + "Trade::SceneFieldData: use a string 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 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), + "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, + _flags{flags|SceneFieldFlag::OffsetOnly|Implementation::implicitSceneFieldFlagsFor(fieldType)}, + _mappingTypeStringType{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeString(fieldType), + "Trade::SceneFieldData: can't use a string constructor for" << fieldType), UnsignedByte(UnsignedByte(mappingType)|(UnsignedShort(fieldType) << 3)))}, _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}, - _fieldStride{(CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, - "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldStride), Short(fieldStride))}, - _fieldType{fieldType}, - _fieldArraySize{(CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), - "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, + _field{ + (CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits but got" << fieldStride), Short(fieldStride)), + Long(stringOffset)}, _fieldData{fieldOffset} {} template Containers::StridedArrayView1D SceneData::mapping(const UnsignedInt fieldId) const { @@ -3158,9 +3775,9 @@ template Containers::StridedArrayView1D SceneData::mutableMapping(co #ifndef CORRADE_NO_ASSERT template bool SceneData::checkFieldTypeCompatibility(const SceneFieldData& field, const char* const prefix) const { - CORRADE_ASSERT(Implementation::SceneFieldTypeTraits::type>::isCompatible(field._fieldType), - prefix << field._name << "is" << field._fieldType << "but requested a type equivalent to" << Implementation::SceneFieldTypeFor::type>::type(), false); - if(field._fieldArraySize) CORRADE_ASSERT(std::is_array::value, + CORRADE_ASSERT(Implementation::SceneFieldTypeTraits::type>::isCompatible(field.fieldType()), + prefix << field._name << "is" << field.fieldType() << "but requested a type equivalent to" << Implementation::SceneFieldTypeFor::type>::type(), false); + if(field.fieldArraySize()) CORRADE_ASSERT(std::is_array::value, prefix << field._name << "is an array field, use T[] to access it", false); else CORRADE_ASSERT(!std::is_array::value, prefix << field._name << "is not an array field, can't use T[] to access it", false); diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index fd4868ec1..ac6ef2f8c 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -76,21 +77,30 @@ struct SceneDataTest: TestSuite::Tester { void constructField(); void constructFieldDefault(); void constructFieldCustom(); + void constructFieldString(); + void constructFieldStringNegativeStride(); + void constructFieldStringNegativeOffset(); void constructField2D(); + void constructField2DString(); void constructFieldTypeErased(); + void constructFieldTypeErasedString(); void constructFieldNonOwningArray(); void constructFieldOffsetOnly(); + void constructFieldOffsetOnlyString(); + void constructFieldOffsetOnlyStringNegativeStride(); void constructFieldArray(); void constructFieldArray2D(); void constructFieldArrayTypeErased(); void constructFieldArrayOffsetOnly(); void constructFieldWrongType(); + void constructFieldWrongTypeString(); void constructFieldInconsistentViewSize(); void constructFieldTooLargeMappingStride(); void constructFieldTooLargeFieldStride(); - void constructFieldOffsetOnlyNotAllowed(); + void constructFieldFlagNotAllowed(); void constructFieldWrongDataAccess(); + void constructFieldWrongStringDataAccess(); void constructField2DWrongSize(); void constructField2DNonContiguous(); void constructFieldArrayNonContiguous(); @@ -103,6 +113,7 @@ struct SceneDataTest: TestSuite::Tester { void constructZeroObjects(); void constructSpecialStrides(); void constructNotOwned(); + template void constructString(); #ifdef MAGNUM_BUILD_DEPRECATED void constructDeprecated(); void constructDeprecatedBoth2DAnd3D(); @@ -113,6 +124,7 @@ struct SceneDataTest: TestSuite::Tester { void constructInconsistentMappingType(); void constructMappingDataNotContained(); void constructFieldDataNotContained(); + void constructStringDataNotContained(); void constructMappingTypeTooSmall(); void constructNotOwnedFlagOwned(); void constructMismatchedTRSViews(); @@ -372,21 +384,30 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructField, &SceneDataTest::constructFieldDefault, &SceneDataTest::constructFieldCustom, + &SceneDataTest::constructFieldString, + &SceneDataTest::constructFieldStringNegativeStride, + &SceneDataTest::constructFieldStringNegativeOffset, &SceneDataTest::constructField2D, + &SceneDataTest::constructField2DString, &SceneDataTest::constructFieldTypeErased, + &SceneDataTest::constructFieldTypeErasedString, &SceneDataTest::constructFieldNonOwningArray, &SceneDataTest::constructFieldOffsetOnly, + &SceneDataTest::constructFieldOffsetOnlyString, + &SceneDataTest::constructFieldOffsetOnlyStringNegativeStride, &SceneDataTest::constructFieldArray, &SceneDataTest::constructFieldArray2D, &SceneDataTest::constructFieldArrayTypeErased, &SceneDataTest::constructFieldArrayOffsetOnly, &SceneDataTest::constructFieldWrongType, + &SceneDataTest::constructFieldWrongTypeString, &SceneDataTest::constructFieldInconsistentViewSize, &SceneDataTest::constructFieldTooLargeMappingStride, &SceneDataTest::constructFieldTooLargeFieldStride, - &SceneDataTest::constructFieldOffsetOnlyNotAllowed, + &SceneDataTest::constructFieldFlagNotAllowed, &SceneDataTest::constructFieldWrongDataAccess, + &SceneDataTest::constructFieldWrongStringDataAccess, &SceneDataTest::constructField2DWrongSize, &SceneDataTest::constructField2DNonContiguous, &SceneDataTest::constructFieldArrayNonContiguous, @@ -402,6 +423,12 @@ SceneDataTest::SceneDataTest() { addInstancedTests({&SceneDataTest::constructNotOwned}, Containers::arraySize(NotOwnedData)); + addTests({ + &SceneDataTest::constructString, + &SceneDataTest::constructString, + &SceneDataTest::constructString, + &SceneDataTest::constructString}); + #ifdef MAGNUM_BUILD_DEPRECATED addInstancedTests({&SceneDataTest::constructDeprecated}, Containers::arraySize(ChildrenDeprecatedData)); @@ -414,6 +441,7 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructInconsistentMappingType, &SceneDataTest::constructMappingDataNotContained, &SceneDataTest::constructFieldDataNotContained, + &SceneDataTest::constructStringDataNotContained, &SceneDataTest::constructMappingTypeTooSmall, &SceneDataTest::constructNotOwnedFlagOwned, &SceneDataTest::constructMismatchedTRSViews, @@ -577,6 +605,7 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::releaseData}); } +using namespace Containers::Literals; using namespace Math::Literals; void SceneDataTest::mappingTypeSizeAlignment() { @@ -817,13 +846,13 @@ void SceneDataTest::constructField() { constexpr SceneField name = crotations.name(); constexpr SceneFieldFlags flags = crotations.flags(); constexpr SceneMappingType mappingType = crotations.mappingType(); - constexpr SceneFieldType fieldType = crotations.fieldType(); - constexpr UnsignedShort fieldArraySize = crotations.fieldArraySize(); CORRADE_COMPARE(name, SceneField::Rotation); CORRADE_COMPARE(flags, SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedShort); - CORRADE_COMPARE(fieldType, SceneFieldType::Complexd); - CORRADE_COMPARE(fieldArraySize, 0); + /* These are not marked constexpr because it'd work only partially, not for + string fields (tested in constructFieldString()) */ + CORRADE_COMPARE(crotations.fieldType(), SceneFieldType::Complexd); + CORRADE_COMPARE(crotations.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 */ @@ -862,6 +891,105 @@ void SceneDataTest::constructFieldCustom() { CORRADE_VERIFY(ranges.fieldData().data() == rangeFieldData); } +void SceneDataTest::constructFieldString() { + const UnsignedLong nameMappingData[3]{}; + + /* The string data is after the field data so the stored offset is + positive. Needs to be put into a struct as otherwise the compiler may + reorder it however it wants. Negative offset is tested in + constructFieldStringNegativeOffset() below. */ + const struct { + Containers::Pair nameField[3]; + char nameString[15]; + } data{}; + CORRADE_VERIFY(static_cast(data.nameString) > static_cast(data.nameField)); + + SceneFieldData names{sceneFieldCustom(25), Containers::arrayView(nameMappingData), data.nameString, SceneFieldType::StringRange16, Containers::arrayView(data.nameField), SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(names.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(names.name(), sceneFieldCustom(25)); + CORRADE_COMPARE(names.size(), 3); + CORRADE_COMPARE(names.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(names.mappingData().size(), 3); + CORRADE_COMPARE(names.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(names.mappingData().data(), nameMappingData); + CORRADE_COMPARE(names.fieldType(), SceneFieldType::StringRange16); + CORRADE_COMPARE(names.fieldArraySize(), 0); + CORRADE_COMPARE(names.fieldData().size(), 3); + CORRADE_COMPARE(names.fieldData().stride(), sizeof(Containers::Pair)); + CORRADE_COMPARE(names.fieldData().data(), data.nameField); + CORRADE_COMPARE(names.stringData(), static_cast(data.nameString)); + + /* This is allowed too for simplicity, the parameter has to be large enough + tho */ + char someArray[3*sizeof(UnsignedLong)]; + CORRADE_COMPARE(names.mappingData(someArray).size(), 3); + CORRADE_COMPARE(names.mappingData(someArray).stride(), sizeof(UnsignedLong)); + CORRADE_VERIFY(names.mappingData(someArray).data() == nameMappingData); + CORRADE_COMPARE(names.fieldData(someArray).size(), 3); + CORRADE_COMPARE(names.fieldData(someArray).stride(), sizeof(Containers::Pair)); + CORRADE_COMPARE(names.fieldData(someArray).data(), data.nameField); + CORRADE_COMPARE(names.stringData(someArray), static_cast(data.nameString)); +} + +void SceneDataTest::constructFieldStringNegativeStride() { + const UnsignedLong nameMappingData[3]{}; + + /* The string data is after the field data so the stored offset is + positive. Needs to be put into a struct as otherwise the compiler may + reorder it however it wants. Negative offset is tested in + constructFieldStringNegativeOffset() below. */ + const struct { + UnsignedInt nameField[3]; + char nameString[15]; + } data{}; + CORRADE_VERIFY(static_cast(data.nameString) > static_cast(data.nameField)); + + SceneFieldData names{sceneFieldCustom(25), Containers::arrayView(nameMappingData), data.nameString, SceneFieldType::StringRangeNullTerminated32, Containers::stridedArrayView(data.nameField).flipped<0>(), SceneFieldFlag::OrderedMapping}; + /* NullTerminatedString added implicitly for this type */ + CORRADE_COMPARE(names.flags(), SceneFieldFlag::OrderedMapping|SceneFieldFlag::NullTerminatedString); + + /* Testing only the properties related to the stride/offset unpack */ + CORRADE_COMPARE(names.fieldData().size(), 3); + CORRADE_COMPARE(names.fieldData().stride(), -sizeof(UnsignedInt)); + CORRADE_COMPARE(names.fieldData().data(), data.nameField + 2); + CORRADE_COMPARE(names.stringData(), static_cast(data.nameString)); + + /* This is a separate code path, but should do the same */ + char someArray[3*sizeof(UnsignedLong)]; + CORRADE_COMPARE(names.fieldData(someArray).size(), 3); + CORRADE_COMPARE(names.fieldData(someArray).stride(), -sizeof(UnsignedInt)); + CORRADE_COMPARE(names.fieldData(someArray).data(), data.nameField + 2); + CORRADE_COMPARE(names.stringData(someArray), static_cast(data.nameString)); +} + +void SceneDataTest::constructFieldStringNegativeOffset() { + const UnsignedLong nameMappingData[3]{}; + + /* Compared to constructFieldString(), the string data is before the field + data so the stored offset is negative. Needd to be put into a struct as + otherwise the compiler may reorder it however it wants. */ + const struct { + char nameString[15]; + Containers::Pair nameField[3]; + } data{}; + CORRADE_VERIFY(static_cast(data.nameString) < static_cast(data.nameField)); + + SceneFieldData names{sceneFieldCustom(25), Containers::arrayView(nameMappingData), data.nameString, SceneFieldType::StringRange16, Containers::arrayView(data.nameField), SceneFieldFlag::NullTerminatedString|SceneFieldFlag::OrderedMapping}; + + /* Testing only the properties related to the stride/offset unpack */ + CORRADE_COMPARE(names.fieldData().size(), 3); + CORRADE_COMPARE(names.fieldData().stride(), sizeof(Containers::Pair)); + CORRADE_COMPARE(names.fieldData().data(), data.nameField); + CORRADE_COMPARE(names.stringData(), static_cast(data.nameString)); + + /* This is a separate code path, but should do the same */ + char someArray[3*sizeof(UnsignedLong)]; + CORRADE_COMPARE(names.fieldData(someArray).size(), 3); + CORRADE_COMPARE(names.fieldData(someArray).stride(), sizeof(Containers::Pair)); + CORRADE_COMPARE(names.fieldData(someArray).data(), data.nameField); + CORRADE_COMPARE(names.stringData(someArray), static_cast(data.nameString)); +} + void SceneDataTest::constructField2D() { char rotationMappingData[6*sizeof(UnsignedShort)]; char rotationFieldData[6*sizeof(Complexd)]; @@ -883,6 +1011,29 @@ void SceneDataTest::constructField2D() { CORRADE_COMPARE(rotations.fieldData().data(), rotationFieldView.data()); } +void SceneDataTest::constructField2DString() { + char nameMappingData[6*sizeof(UnsignedLong)]{}; + char nameFieldData[6*sizeof(Containers::Pair)]{}; + auto nameMappingView = Containers::StridedArrayView2D{nameMappingData, {6, sizeof(UnsignedLong)}}.every(2); + auto nameFieldView = Containers::StridedArrayView2D{nameFieldData, {6, sizeof(Containers::Pair)}}.every(2); + const char nameStringData[15]{}; + + SceneFieldData names{sceneFieldCustom(25), nameMappingView, nameStringData, SceneFieldType::StringRange16, nameFieldView, SceneFieldFlag::NullTerminatedString|SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(names.flags(), SceneFieldFlag::OrderedMapping|SceneFieldFlag::NullTerminatedString); + CORRADE_COMPARE(names.name(), sceneFieldCustom(25)); + CORRADE_COMPARE(names.size(), 3); + CORRADE_COMPARE(names.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(names.mappingData().size(), 3); + CORRADE_COMPARE(names.mappingData().stride(), 2*sizeof(UnsignedLong)); + CORRADE_COMPARE(names.mappingData().data(), nameMappingView.data()); + CORRADE_COMPARE(names.fieldType(), SceneFieldType::StringRange16); + CORRADE_COMPARE(names.fieldArraySize(), 0); + CORRADE_COMPARE(names.fieldData().size(), 3); + CORRADE_COMPARE(names.fieldData().stride(), 2*sizeof(UnsignedShort)*2); + CORRADE_COMPARE(names.fieldData().data(), nameFieldView.data()); + CORRADE_COMPARE(names.stringData(), static_cast(nameStringData)); +} + void SceneDataTest::constructFieldTypeErased() { const UnsignedLong scalingMappingData[3]{}; const Vector3 scalingFieldData[3]; @@ -901,6 +1052,27 @@ void SceneDataTest::constructFieldTypeErased() { CORRADE_COMPARE(scalings.fieldData().data(), scalingFieldData); } +void SceneDataTest::constructFieldTypeErasedString() { + const UnsignedLong nameMappingData[3]{}; + const char nameStringData[15]{}; + const Containers::Pair nameFieldData[3]{}; + + SceneFieldData names{sceneFieldCustom(25), SceneMappingType::UnsignedLong, Containers::arrayCast(Containers::stridedArrayView(nameMappingData)), nameStringData, SceneFieldType::StringRange16, Containers::arrayCast(Containers::stridedArrayView(nameFieldData)), SceneFieldFlag::NullTerminatedString|SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(names.flags(), SceneFieldFlag::OrderedMapping|SceneFieldFlag::NullTerminatedString); + CORRADE_COMPARE(names.name(), sceneFieldCustom(25)); + CORRADE_COMPARE(names.size(), 3); + CORRADE_COMPARE(names.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(names.mappingData().size(), 3); + CORRADE_COMPARE(names.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(names.mappingData().data(), nameMappingData); + CORRADE_COMPARE(names.fieldType(), SceneFieldType::StringRange16); + CORRADE_COMPARE(names.fieldArraySize(), 0); + CORRADE_COMPARE(names.fieldData().size(), 3); + CORRADE_COMPARE(names.fieldData().stride(), sizeof(UnsignedShort)*2); + CORRADE_COMPARE(names.fieldData().data(), nameFieldData); + CORRADE_COMPARE(names.stringData(), static_cast(nameStringData)); +} + void SceneDataTest::constructFieldNonOwningArray() { const SceneFieldData data[3]; Containers::Array array = sceneFieldDataNonOwningArray(data); @@ -937,6 +1109,70 @@ void SceneDataTest::constructFieldOffsetOnly() { TestSuite::Compare::Container); } +void SceneDataTest::constructFieldOffsetOnlyString() { + const char string[] = "NAMES:eyehandnoseleg"; + struct Data { + Byte parent; + UnsignedLong object; + Containers::Pair nameRange; + } data[]{ + {0, 2, {3, 4}}, + {0, 15, {11, 3}} + }; + + SceneFieldData a{sceneFieldCustom(36), 2, SceneMappingType::UnsignedLong, offsetof(Data, object), sizeof(Data), 6, SceneFieldType::StringRange8, offsetof(Data, nameRange), sizeof(Data), SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(a.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(a.name(), sceneFieldCustom(36)); + CORRADE_COMPARE(a.size(), 2); + CORRADE_COMPARE(a.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(a.mappingData(data).size(), 2); + CORRADE_COMPARE(a.mappingData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.mappingData(data)), + Containers::arrayView({2, 15}), + TestSuite::Compare::Container); + CORRADE_COMPARE(a.fieldType(), SceneFieldType::StringRange8); + CORRADE_COMPARE(a.fieldArraySize(), 0); + CORRADE_COMPARE(a.fieldData(data).size(), 2); + CORRADE_COMPARE(a.fieldData(data).stride(), sizeof(Data)); + + auto fieldData = Containers::arrayCast>(a.fieldData(data)); + CORRADE_COMPARE_AS(fieldData, (Containers::arrayView>({ + {3, 4}, {11, 3} + })), TestSuite::Compare::Container); + CORRADE_COMPARE(a.stringData(string), "eyehandnoseleg"_s); + CORRADE_COMPARE((Containers::StringView{a.stringData(string) + fieldData[0].first(), fieldData[0].second()}), "hand"); + CORRADE_COMPARE((Containers::StringView{a.stringData(string) + fieldData[1].first(), fieldData[1].second()}), "leg"); +} + +void SceneDataTest::constructFieldOffsetOnlyStringNegativeStride() { + const char string[] = "NAMES:eye\0hand\0nose\0leg"; + struct Data { + UnsignedLong object; + UnsignedShort nameRange; + } data[]{ + {2, 4}, + {15, 14} + }; + + SceneFieldData a{sceneFieldCustom(36), 2, SceneMappingType::UnsignedLong, offsetof(Data, object), sizeof(Data), 6, SceneFieldType::StringRangeNullTerminated16, offsetof(Data, nameRange) + sizeof(Data), -std::ptrdiff_t(sizeof(Data)), SceneFieldFlag::OrderedMapping}; + + /* NullTerminatedString added implicitly for this type */ + CORRADE_COMPARE(a.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping|SceneFieldFlag::NullTerminatedString); + CORRADE_COMPARE(a.fieldType(), SceneFieldType::StringRangeNullTerminated16); + CORRADE_COMPARE(a.fieldArraySize(), 0); + CORRADE_COMPARE(a.fieldData(data).size(), 2); + CORRADE_COMPARE(a.fieldData(data).stride(), -sizeof(Data)); + + /* Order flipped compared to constructFieldOffsetOnlyString() */ + auto fieldData = Containers::arrayCast(a.fieldData(data)); + CORRADE_COMPARE_AS(fieldData, Containers::arrayView({ + 14, 4, + }), TestSuite::Compare::Container); + CORRADE_COMPARE(a.stringData(string), "eye"_s); /* after \0 it's lost */ + CORRADE_COMPARE(a.stringData(string) + fieldData[0], "leg"_s); + CORRADE_COMPARE(a.stringData(string) + fieldData[1], "hand"_s); +} + constexpr UnsignedByte ArrayOffsetMappingData[3]{}; constexpr Int ArrayOffsetFieldData[3*4]{}; @@ -1044,13 +1280,18 @@ void SceneDataTest::constructFieldArrayOffsetOnly() { void SceneDataTest::constructFieldInconsistentViewSize() { CORRADE_SKIP_IF_NO_ASSERT(); - const UnsignedShort rotationMappingData[3]{}; + const UnsignedShort mappingData[3]{}; const Complexd rotationFieldData[2]; + const char helloStringData[5]{}; + const UnsignedLong helloOffsetsData[2]{}; std::ostringstream out; Error redirectError{&out}; - SceneFieldData{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; - 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"); + SceneFieldData{SceneField::Rotation, Containers::arrayView(mappingData), Containers::arrayView(rotationFieldData)}; + 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(32) mapping and field view to have the same size but got 3 and 2\n"); } void SceneDataTest::constructFieldWrongType() { @@ -1063,16 +1304,52 @@ 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 */ 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::constructFieldWrongTypeString() { + CORRADE_SKIP_IF_NO_ASSERT(); + + const UnsignedShort mappingData[3]{}; + const Complexd rotationFieldData[3]; + const char helloStringData[5]{}; + const UnsignedLong helloFieldData[3]{}; + + std::ostringstream out; + Error redirectError{&out}; + /* Non-string constructors with a string SceneFieldType. Only type-erased, + 2D and offset-only construction, the regular and array constructor from + typed views have SceneFieldType implicit. */ + SceneFieldData{sceneFieldCustom(32), SceneMappingType::UnsignedShort, Containers::arrayView(mappingData), SceneFieldType::StringOffset64, Containers::arrayView(helloFieldData)}; + SceneFieldData{sceneFieldCustom(32), Containers::arrayCast<2, const char>(Containers::arrayView(mappingData)), SceneFieldType::StringOffset64, Containers::arrayCast<2, const char>(Containers::arrayView(helloFieldData))}; + SceneFieldData{sceneFieldCustom(32), 3, SceneMappingType::UnsignedShort, 0, 2, SceneFieldType::StringRange16, 0, 8}; + + /* String constructors with a non-string SceneFieldType. */ + SceneFieldData{SceneField::Rotation, SceneMappingType::UnsignedShort, Containers::arrayView(mappingData), nullptr, SceneFieldType::Complexd, Containers::arrayView(rotationFieldData)}; + SceneFieldData{SceneField::Rotation, Containers::arrayCast<2, const char>(Containers::arrayView(mappingData)), helloStringData, SceneFieldType::Complexd, Containers::arrayCast<2, const char>(Containers::arrayView(rotationFieldData))}; + SceneFieldData{SceneField::Rotation, Containers::arrayView(mappingData), helloStringData, SceneFieldType::Complexd, Containers::arrayView(rotationFieldData)}; + SceneFieldData{SceneField::Rotation, 3, SceneMappingType::UnsignedLong, 0, 8, 0, SceneFieldType::Quaternion, 0, 16}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: use a string constructor for Trade::SceneFieldType::StringOffset64\n" + "Trade::SceneFieldData: use a string constructor for Trade::SceneFieldType::StringOffset64\n" + "Trade::SceneFieldData: use a string constructor for Trade::SceneFieldType::StringRange16\n" + + "Trade::SceneFieldData: can't use a string constructor for Trade::SceneFieldType::Complexd\n" + "Trade::SceneFieldData: can't use a string constructor for Trade::SceneFieldType::Complexd\n" + "Trade::SceneFieldData: can't use a string constructor for Trade::SceneFieldType::Complexd\n" + "Trade::SceneFieldData: can't use a string constructor for Trade::SceneFieldType::Quaternion\n"); +} + void SceneDataTest::constructFieldTooLargeMappingStride() { CORRADE_SKIP_IF_NO_ASSERT(); UnsignedInt enough[2]; char toomuch[2*(32768 + sizeof(UnsignedInt))]; + const char helloStringData[5]{}; /* These should be fine */ SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}, SceneFieldType::UnsignedInt, enough}; @@ -1080,13 +1357,28 @@ 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(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}; + SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 65536, -32768, 0, SceneFieldType::StringOffset32, 0, 4}; + std::ostringstream out; Error redirectError{&out}; SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}, SceneFieldType::UnsignedInt, enough}; SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>(), SceneFieldType::UnsignedInt, enough}; 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(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}; + SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 65538, -32769, 0, SceneFieldType::StringOffset32, 0, 4}; CORRADE_COMPARE(out.str(), + "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" @@ -1098,6 +1390,7 @@ void SceneDataTest::constructFieldTooLargeFieldStride() { UnsignedInt enough[2]; char toomuch[2*(32768 + sizeof(UnsignedInt))]; + const char helloStringData[5]{}; /* These should be fine */ SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}}; @@ -1105,32 +1398,59 @@ 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(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}; + SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 0, 4, 0, SceneFieldType::StringRangeNullTerminated32, 65536, -32768}; + std::ostringstream out; Error redirectError{&out}; SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}}; SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>()}; 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(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}; + SceneFieldData{sceneFieldCustom(35), 2, SceneMappingType::UnsignedInt, 0, 4, 0, SceneFieldType::StringRangeNullTerminated32, 65538, -32769}; CORRADE_COMPARE(out.str(), + "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"); } -void SceneDataTest::constructFieldOffsetOnlyNotAllowed() { +void SceneDataTest::constructFieldFlagNotAllowed() { CORRADE_SKIP_IF_NO_ASSERT(); - const UnsignedShort rotationMappingData[3]{}; + const UnsignedShort mappingData[3]{}; const Quaternion rotationFieldData[3]; + const char helloStringData[5]{}; + const UnsignedShort helloFieldData[3]{}; - /* This one is fine */ + /* These are fine */ SceneFieldData{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 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}; std::ostringstream out; Error redirectError{&out}; - SceneFieldData{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OffsetOnly}; + SceneFieldData{SceneField::Rotation, Containers::arrayView(mappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OffsetOnly}; + 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(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\n"); } @@ -1139,62 +1459,115 @@ void SceneDataTest::constructFieldWrongDataAccess() { const UnsignedShort rotationMappingData[3]{}; const Quaternion rotationFieldData[3]; + const char hello[5]{}; + 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}; CORRADE_COMPARE(a.flags(), SceneFieldFlags{}); CORRADE_COMPARE(b.flags(), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(c.flags(), SceneFieldFlag::OffsetOnly); a.mappingData(rotationMappingData); /* This is fine, no asserts */ a.fieldData(rotationFieldData); + c.stringData(hello); std::ostringstream out; Error redirectError{&out}; b.mappingData(); b.fieldData(); + c.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::fieldData(): 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::constructFieldWrongStringDataAccess() { + CORRADE_SKIP_IF_NO_ASSERT(); + + const UnsignedShort rotationMappingData[3]{}; + const Quaternion rotationFieldData[3]; + + SceneFieldData a{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; + + std::ostringstream out; + Error redirectError{&out}; + a.stringData(); + a.stringData(rotationFieldData); + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData::stringData(): the field is Trade::SceneFieldType::Quaternion, not a string\n" + "Trade::SceneFieldData::stringData(): the field is Trade::SceneFieldType::Quaternion, not a string\n"); } void SceneDataTest::constructField2DWrongSize() { CORRADE_SKIP_IF_NO_ASSERT(); - char rotationMappingData[5*sizeof(UnsignedInt)]; - char rotationFieldData[5*sizeof(Complex)]; + char mappingData[5*sizeof(UnsignedInt)]; + char rotationFieldData[4*sizeof(Complex)]; + char helloStringData[3]{}; + char helloFieldData[4*sizeof(UnsignedShort)]{}; std::ostringstream out; Error redirectError{&out}; SceneFieldData{SceneField::Rotation, - Containers::StridedArrayView2D{rotationMappingData, {4, 5}}.every(2), + Containers::StridedArrayView2D{mappingData, {4, 5}}.every(2), SceneFieldType::Complex, Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + SceneFieldData{sceneFieldCustom(32), + Containers::StridedArrayView2D{mappingData, {4, 5}}.every(2), + helloStringData, + SceneFieldType::StringOffset16, + Containers::StridedArrayView2D{helloFieldData, {4, sizeof(UnsignedShort)}}.every(2)}; SceneFieldData{SceneField::Translation, - Containers::StridedArrayView2D{rotationMappingData, {4, sizeof(UnsignedInt)}}.every(2), + Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedInt)}}.every(2), SceneFieldType::Vector3, Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + 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: second field view dimension size 8 doesn't match Trade::SceneFieldType::Vector3\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" + "Trade::SceneFieldData: second field view dimension size 2 doesn't match Trade::SceneFieldType::StringRange16\n"); } void SceneDataTest::constructField2DNonContiguous() { CORRADE_SKIP_IF_NO_ASSERT(); - char rotationMappingData[8*sizeof(UnsignedInt)]; + char mappingData[8*sizeof(UnsignedInt)]; char rotationFieldData[8*sizeof(Complex)]; + char helloStringData[3]{}; + char helloFieldData[8*sizeof(UnsignedShort)]{}; std::ostringstream out; Error redirectError{&out}; SceneFieldData{SceneField::Rotation, - Containers::StridedArrayView2D{rotationMappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), + Containers::StridedArrayView2D{mappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), SceneFieldType::Complex, Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}}; + SceneFieldData{sceneFieldCustom(32), + Containers::StridedArrayView2D{mappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), + helloStringData, + SceneFieldType::StringOffset16, + Containers::StridedArrayView2D{helloFieldData, {4, sizeof(UnsignedShort)}}}; SceneFieldData{SceneField::Rotation, - Containers::StridedArrayView2D{rotationMappingData, {4, sizeof(UnsignedInt)}}, + Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedInt)}}, SceneFieldType::Complex, Containers::StridedArrayView2D{rotationFieldData, {4, 2*sizeof(Complex)}}.every({1, 2})}; + SceneFieldData{sceneFieldCustom(32), + /* Just to cover the case of a 1-byte mapping type (lazy) */ + Containers::StridedArrayView2D{mappingData, {4, sizeof(UnsignedByte)}, {4, 1}}, + helloStringData, + 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 field view dimension is not contiguous\n" "Trade::SceneFieldData: second field view dimension is not contiguous\n"); } @@ -1253,6 +1626,9 @@ void SceneDataTest::constructFieldArrayNotAllowed() { SceneFieldData{SceneField::Rotation, rotationMappingChar, SceneFieldType::Quaternion, rotationField2DChar, 3}; + /* String fields can't be arrays, but for those the constructor doesn't + even offer the array size; and constructing them with the regular + constructor will fail as tested in constructFieldWrongTypeString() */ CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" @@ -1717,6 +2093,320 @@ void SceneDataTest::constructNotOwned() { CORRADE_COMPARE(scene.mutableField(0)[2], 0); } +template struct StringFieldTraits; +template<> struct StringFieldTraits { + static const char* name() { return "8"; } + static SceneFieldType offsetType() { return SceneFieldType::StringOffset8; } + static SceneFieldType rangeType() { return SceneFieldType::StringRange8; } + static SceneFieldType rangeNullTerminatedType() { + return SceneFieldType::StringRangeNullTerminated8; + } +}; +template<> struct StringFieldTraits { + static const char* name() { return "16"; } + static SceneFieldType offsetType() { return SceneFieldType::StringOffset16; } + static SceneFieldType rangeType() { return SceneFieldType::StringRange16; } + static SceneFieldType rangeNullTerminatedType() { + return SceneFieldType::StringRangeNullTerminated16; + } +}; +template<> struct StringFieldTraits { + static const char* name() { return "32"; } + static SceneFieldType offsetType() { return SceneFieldType::StringOffset32; } + static SceneFieldType rangeType() { return SceneFieldType::StringRange32; } + static SceneFieldType rangeNullTerminatedType() { + return SceneFieldType::StringRangeNullTerminated32; + } +}; +template<> struct StringFieldTraits { + static const char* name() { return "64"; } + static SceneFieldType offsetType() { return SceneFieldType::StringOffset64; } + static SceneFieldType rangeType() { return SceneFieldType::StringRange64; } + static SceneFieldType rangeNullTerminatedType() { + return SceneFieldType::StringRangeNullTerminated64; + } +}; + +template void SceneDataTest::constructString() { + setTestCaseTemplateName(StringFieldTraits::name()); + + /* Assumption is that these will be populated by some helper in SceneTools, + with offsets/sizes remembered. Things tested: + + - names are offsets w/o null termination, thus have to be referenced + only once and in the order they are in the string + - keys are null-terminated ranges, thus can be referenced multiple + times, the only complication is that there's an extra \0 after + which doesn't count into the size + - values are non-null-terminated (offset, size) ranges, nothing + special about these, can be also referenced multiple times + including subsets (such as "brown" out of "lightbrown" + - files are offsets w/ null terminated, thus again have to be + referenced only once an in the order they are in the string, the + implicit size has to exclude the null terminator + - keys are null terminated (offset, implicit size) ranges, are + defined at the end of the string in order to verify a null + terminator gets added by ArrayTuple after, compared to plain + offsets the reference order doesn't matter and can be referenced + multiple times */ + + Containers::StringView namesKeysValues = + "Chair" /* 5 */ + "Lampshade" /* 14 */ + "color\0" /* 14, 5 */ + "age\0" /* 20, 3 */ + "lightbrown" /* 24, 10; 29, 5 */ + "old" /* 34, 3 */ + "new"_s; /* 37, 3 doesn't assume null termination */ + CORRADE_COMPARE(namesKeysValues.size(), 37 + 3); + + Containers::StringView filesTags = + "chair.glb\0" /* 0 */ + "empty.obj\0" /* 10 */ + "lampshade.fbx\0" /* 20 */ + "MAPPABLE\0" /* 34 */ + "STRANGE"_s; /* 43, assumes it's stored null-terminated */ + CORRADE_COMPARE(filesTags.size(), 43 + 7); + + struct Name { + UnsignedShort object; + T nameOffset; + }; + + struct KeyValue { + UnsignedShort object; + Containers::Pair keyRangeNullTerminated; + Containers::Pair valueRange; + }; + + struct FileTag { + UnsignedShort object; + T fileOffsetNullTerminated; + T tagRangeNullTerminated; + }; + + Containers::StridedArrayView1D nameData; + Containers::StridedArrayView1D keyValueData; + Containers::MutableStringView nameKeyValueStringData; + Containers::StridedArrayView1D fileTagData; + Containers::MutableStringView fileTagStringData; + Containers::ArrayTuple data{ + {NoInit, 2, nameData}, + {NoInit, 4, keyValueData}, + {NoInit, filesTags.size(), fileTagStringData, Containers::StringViewFlag::NullTerminated}, + {NoInit, 3, fileTagData}, + {NoInit, namesKeysValues.size(), nameKeyValueStringData} + }; + + /* The offset has to be monotonically increasing, so the view is flipped in + the SceneFieldData */ + Utility::copy({ + {3, 14}, /* Chair */ + {1, 5} /* Lampshade */ + }, nameData); + + Utility::copy({ + {3, {20, 3}, {37, 3}}, /* age=new */ + {3, {14, 5}, {24, 10}}, /* color=lightbrown */ + {1, {20, 3}, {34, 3}}, /* age=old */ + {1, {14, 5}, {29, 5}} /* color=brown */ + }, keyValueData); + + Utility::copy({ + {1, 10, 34}, /* chair.glb, MAPPABLE */ + {2, 20, 43}, /* empty.obj, STRANGE */ + {3, 34, 43}, /* lampshade.fbx, STRANGE */ + }, fileTagData); + + Utility::copy(namesKeysValues, nameKeyValueStringData); + Utility::copy(filesTags, fileTagStringData); + + const SceneField nameField = sceneFieldCustom(5); + const SceneField keyField = sceneFieldCustom(6); + const SceneField valueField = sceneFieldCustom(7); + const SceneField fileField = sceneFieldCustom(8); + const SceneField tagField = sceneFieldCustom(9); + + /* Calculate offsets for the offset-only field before the data is moved + out */ + const std::size_t keyValueDataOffset = static_cast(keyValueData.data()) - data.data(); + const std::size_t nameKeyValueStringDataOffset = nameKeyValueStringData.data() - data.data(); + + /* The fileTagStringData should be before the file/tag field data in order + to test storing negative string data offset */ + CORRADE_VERIFY(fileTagStringData.data() < fileTagData.data()); + + SceneData scene{SceneMappingType::UnsignedShort, 4, std::move(data), { + /* Has a negative stride */ + SceneFieldData{nameField, nameData.slice(&Name::object).template flipped<0>(), + nameKeyValueStringData.data(), StringFieldTraits::offsetType(), + nameData.slice(&Name::nameOffset).template flipped<0>()}, + SceneFieldData{keyField, keyValueData.slice(&KeyValue::object), + nameKeyValueStringData.data(), StringFieldTraits::rangeType(), + keyValueData.slice(&KeyValue::keyRangeNullTerminated), + SceneFieldFlag::NullTerminatedString}, + /* Offset-only */ + SceneFieldData{valueField, 4, + SceneMappingType::UnsignedShort, keyValueDataOffset + offsetof(KeyValue, object), sizeof(KeyValue), + nameKeyValueStringDataOffset, + StringFieldTraits::rangeType(), keyValueDataOffset + offsetof(KeyValue, valueRange), sizeof(KeyValue)}, + /* These two have the string data defined *before* the field data, thus + storing a negative string data offset */ + SceneFieldData{fileField, fileTagData.slice(&FileTag::object), + fileTagStringData.data(), StringFieldTraits::offsetType(), + fileTagData.slice(&FileTag::fileOffsetNullTerminated), + SceneFieldFlag::NullTerminatedString}, + SceneFieldData{tagField, fileTagData.slice(&FileTag::object), + fileTagStringData.data(), StringFieldTraits::rangeNullTerminatedType(), + fileTagData.slice(&FileTag::tagRangeNullTerminated)}, + }}; + + /* Raw field data access to verify it correctly special-cases */ + CORRADE_COMPARE(scene.fieldData(1).mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.fieldData(1).mappingData().data(), keyValueData.data()); + CORRADE_COMPARE(scene.fieldData(1).stringData(), nameKeyValueStringData.data()); + CORRADE_COMPARE(scene.fieldData(1).fieldType(), StringFieldTraits::rangeType()); + CORRADE_COMPARE(scene.fieldData(1).fieldData().data(), keyValueData.slice(&KeyValue::keyRangeNullTerminated).data()); + CORRADE_COMPARE(scene.fieldData(1).fieldArraySize(), 0); + CORRADE_COMPARE(scene.fieldData(1).flags(), SceneFieldFlag::NullTerminatedString); + + /* Field property access, to verify it correctly special-cases */ + CORRADE_COMPARE(scene.fieldType(keyField), StringFieldTraits::rangeType()); + CORRADE_COMPARE(scene.fieldArraySize(keyField), 0); + + /* Field flags should contain the string-specific flags */ + CORRADE_COMPARE(scene.fieldFlags(nameField), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldFlags(keyField), SceneFieldFlag::NullTerminatedString); + CORRADE_COMPARE(scene.fieldFlags(valueField), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(scene.fieldFlags(fileField), SceneFieldFlag::NullTerminatedString); + /* This one is added implicitly */ + CORRADE_COMPARE(scene.fieldFlags(tagField), SceneFieldFlag::NullTerminatedString); + + /* Mapping access should correctly special-case the string type stored in + the same byte */ + CORRADE_COMPARE_AS(scene.mapping(nameField), Containers::arrayView({ + 1, 3 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.mutableMapping(nameField), Containers::stridedArrayView({ + 1, 3 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.mappingAsArray(nameField), Containers::arrayView({ + 1, 3 + }), TestSuite::Compare::Container); + + /* Raw field data access. ID vs name of this API tested thoroughly enough + in construct(). */ + CORRADE_COMPARE_AS(scene.field(nameField), Containers::arrayView({ + 5, 14 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.field>(keyField)), (Containers::arrayView>({ + {20, 3}, {14, 5}, {20, 3}, {14, 5} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.field>(valueField)), (Containers::arrayView>({ + {37, 3}, {24, 10}, {34, 3}, {29, 5} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(fileField), Containers::arrayView({ + 10, 20, 34 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(tagField), Containers::arrayView({ + 34, 43, 43 + }), TestSuite::Compare::Container); + + /* Raw string data access using an ID */ + CORRADE_COMPARE(scene.fieldStringData(0), static_cast(nameKeyValueStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(1), static_cast(nameKeyValueStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(2), static_cast(nameKeyValueStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(3), static_cast(fileTagStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(4), static_cast(fileTagStringData.data())); + + /* Raw string data access using a name */ + CORRADE_COMPARE(scene.fieldStringData(nameField), static_cast(nameKeyValueStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(keyField), static_cast(nameKeyValueStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(valueField), static_cast(nameKeyValueStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(fileField), static_cast(fileTagStringData.data())); + CORRADE_COMPARE(scene.fieldStringData(tagField), static_cast(fileTagStringData.data())); + + /* String access using an ID */ + CORRADE_COMPARE_AS(scene.fieldStrings(0), Containers::arrayView({ + "Chair"_s, "Lampshade"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(0)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlags{}); + } + CORRADE_COMPARE_AS(scene.fieldStrings(1), Containers::arrayView({ + "age"_s, "color"_s, "age"_s, "color"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(1)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(i[i.size()], '\0'); + } + CORRADE_COMPARE_AS(scene.fieldStrings(2), Containers::arrayView({ + "new"_s, "lightbrown"_s, "old"_s, "brown"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(2)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlags{}); + } + CORRADE_COMPARE_AS(scene.fieldStrings(3), Containers::arrayView({ + "chair.glb"_s, "empty.obj"_s, "lampshade.fbx"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(3)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(i[i.size()], '\0'); + } + CORRADE_COMPARE_AS(scene.fieldStrings(4), Containers::arrayView({ + "MAPPABLE"_s, "STRANGE"_s, "STRANGE"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(4)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(i[i.size()], '\0'); + } + + /* String access using a name */ + CORRADE_COMPARE_AS(scene.fieldStrings(nameField), Containers::arrayView({ + "Chair"_s, "Lampshade"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(nameField)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlags{}); + } + CORRADE_COMPARE_AS(scene.fieldStrings(keyField), Containers::arrayView({ + "age"_s, "color"_s, "age"_s, "color"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(keyField)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(i[i.size()], '\0'); + } + CORRADE_COMPARE_AS(scene.fieldStrings(valueField), Containers::arrayView({ + "new"_s, "lightbrown"_s, "old"_s, "brown"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(valueField)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlags{}); + } + CORRADE_COMPARE_AS(scene.fieldStrings(fileField), Containers::arrayView({ + "chair.glb"_s, "empty.obj"_s, "lampshade.fbx"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(fileField)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(i[i.size()], '\0'); + } + CORRADE_COMPARE_AS(scene.fieldStrings(tagField), Containers::arrayView({ + "MAPPABLE"_s, "STRANGE"_s, "STRANGE"_s + }), TestSuite::Compare::Container); + for(Containers::StringView i: scene.fieldStrings(tagField)) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(i[i.size()], '\0'); + } +} + #ifdef MAGNUM_BUILD_DEPRECATED void SceneDataTest::constructDeprecated() { auto&& data = ChildrenDeprecatedData[testCaseInstanceId()]; @@ -1924,6 +2614,42 @@ 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::constructStringDataNotContained() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Containers::ArrayView data{reinterpret_cast(0xbadda9), 10}; + Containers::ArrayView dataIn{reinterpret_cast(0xbadda9), 5}; + + /* This should be fine even though it points to the very end (the string + array could be empty) */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{sceneFieldCustom(35), dataIn, + reinterpret_cast(0xbadda9 + 10), SceneFieldType::StringOffset16, + dataIn} + }}; + + std::ostringstream out; + Error redirectError{&out}; + /* Data too early */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + /* This is here to test that not just the first attribute gets checked + and that the message shows proper ID */ + SceneFieldData{SceneField::Light, dataIn, dataIn}, + SceneFieldData{sceneFieldCustom(35), dataIn, + reinterpret_cast(0xbadda9 - 1), SceneFieldType::StringOffset16, + dataIn} + }}; + /* Data too late */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{sceneFieldCustom(35), dataIn, + reinterpret_cast(0xbaddaa9 + 11), SceneFieldType::StringRange8, + dataIn} + }}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: field string data 0xbadda8 of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field string data 0xbaddab4 of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n"); +} + void SceneDataTest::constructMappingTypeTooSmall() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -4635,6 +5361,9 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(2); scene.mutableField(2); + scene.fieldStringData(2); + scene.fieldStrings(2); + scene.fieldId(sceneFieldCustom(666)); scene.fieldFlags(sceneFieldCustom(666)); scene.findFieldObjectOffset(sceneFieldCustom(666), 0); @@ -4650,6 +5379,9 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(sceneFieldCustom(666)); scene.mutableField(sceneFieldCustom(666)); + scene.fieldStringData(sceneFieldCustom(666)); + scene.fieldStrings(sceneFieldCustom(666)); + scene.parentsAsArray(); scene.parentsInto(nullptr, nullptr); scene.parentsInto(0, nullptr, nullptr); @@ -4698,6 +5430,9 @@ 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::fieldStringData(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldStrings(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldId(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::fieldFlags(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::findFieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" @@ -4713,6 +5448,9 @@ 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::fieldStringData(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldStrings(): field Trade::SceneField::Custom(666) not found\n" + /* AsArray() and Into() each share a common helper but have different top-level code paths. They however have the same assertion messages to save binary size a bit. */ @@ -4771,19 +5509,34 @@ void SceneDataTest::fieldWrongType() { scene.field(1); scene.mutableField(1); scene.mutableField(1); + + scene.fieldStringData(1); + scene.fieldStrings(1); + scene.field(SceneField::Mesh); scene.field(SceneField::Mesh); scene.mutableField(SceneField::Mesh); scene.mutableField(SceneField::Mesh); + + scene.fieldStringData(SceneField::Mesh); + scene.fieldStrings(SceneField::Mesh); + CORRADE_COMPARE(out.str(), "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::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" + "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::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\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"); } void SceneDataTest::fieldWrongPointerType() {