diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 1ae8f15b2..b75787194 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -499,22 +499,32 @@ Containers::ArrayView SceneData::mutableData() & { return _data; } -Containers::StridedArrayView1D SceneData::fieldDataObjectViewInternal(const SceneFieldData& field) const { +Containers::StridedArrayView1D SceneData::fieldDataObjectViewInternal(const SceneFieldData& field, const std::size_t offset, const std::size_t size) const { + CORRADE_INTERNAL_ASSERT(offset + size <= field._size); return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ - /** @todo better ideas for the StridedArrayView API? */ - {field._isOffsetOnly ? _data.data() + field._objectData.offset : - field._objectData.pointer, ~std::size_t{}}, - field._size, field._objectStride}; + {static_cast(field._isOffsetOnly ? + _data.data() + field._objectData.offset : field._objectData.pointer) + + field._fieldStride*offset, ~std::size_t{}}, + size, field._objectStride}; } -Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field) const { +Containers::StridedArrayView1D SceneData::fieldDataObjectViewInternal(const SceneFieldData& field) const { + return fieldDataObjectViewInternal(field, 0, field._size); +} + +Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field, const std::size_t offset, const std::size_t size) const { + CORRADE_INTERNAL_ASSERT(offset + size <= field._size); return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ - /** @todo better ideas for the StridedArrayView API? */ - {field._isOffsetOnly ? _data.data() + field._fieldData.offset : - field._fieldData.pointer, ~std::size_t{}}, - field._size, field._fieldStride}; + {static_cast(field._isOffsetOnly ? + _data.data() + field._fieldData.offset : field._fieldData.pointer) + + field._fieldStride*offset, ~std::size_t{}}, + size, field._fieldStride}; +} + +Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field) const { + return fieldDataFieldViewInternal(field, 0, field._size); } SceneFieldData SceneData::fieldData(const UnsignedInt id) const { @@ -668,13 +678,12 @@ Containers::StridedArrayView2D SceneData::mutableField(const SceneField na return mutableField(fieldId); } -void SceneData::objectsInto(const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const { - CORRADE_ASSERT(fieldId < _fields.size(), - "Trade::SceneData::objectsInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", ); +void SceneData::objectsIntoInternal(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 */ + const SceneFieldData& field = _fields[fieldId]; - CORRADE_ASSERT(destination.size() == field._size, - "Trade::SceneData::objectsInto(): expected a view with" << field._size << "elements but got" << destination.size(), ); - const Containers::StridedArrayView1D objectData = fieldDataObjectViewInternal(field); + const Containers::StridedArrayView1D objectData = fieldDataObjectViewInternal(field, offset, destination.size()); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); if(field._objectType == SceneObjectType::UnsignedInt) @@ -689,13 +698,31 @@ void SceneData::objectsInto(const UnsignedInt fieldId, const Containers::Strided } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } +void SceneData::objectsInto(const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::objectsInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::objectsInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + objectsIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::objectsInto(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::objectsInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::objectsInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + objectsIntoInternal(fieldId, offset, destination.prefix(size)); + return size; +} + Containers::Array SceneData::objectsAsArray(const UnsignedInt fieldId) const { CORRADE_ASSERT(fieldId < _fields.size(), /* Using the same message as in Into() to avoid too many redundant strings in the binary */ "Trade::SceneData::objectsInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; - objectsInto(fieldId, out); + objectsIntoInternal(fieldId, 0, out); return out; } @@ -706,6 +733,13 @@ void SceneData::objectsInto(const SceneField name, const Containers::StridedArra objectsInto(fieldId, destination); } +std::size_t SceneData::objectsInto(const SceneField name, std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::objectsInto(): field" << name << "not found", {}); + return objectsInto(fieldId, offset, destination); +} + Containers::Array SceneData::objectsAsArray(const SceneField name) const { const UnsignedInt fieldId = fieldFor(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, @@ -715,13 +749,12 @@ Containers::Array SceneData::objectsAsArray(const SceneField name) return objectsAsArray(fieldId); } -void SceneData::parentsIntoInternal(const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const { - CORRADE_ASSERT(fieldId != ~UnsignedInt{}, - "Trade::SceneData::parentsInto(): field not found", ); +void SceneData::parentsIntoInternal(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 */ + const SceneFieldData& field = _fields[fieldId]; - CORRADE_ASSERT(destination.size() == field._size, - "Trade::SceneData::parentsInto(): expected a view with" << field._size << "elements but got" << destination.size(), ); - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1i = Containers::arrayCast<2, Int>(destination); if(field._fieldType == SceneFieldType::Int) @@ -737,7 +770,23 @@ void SceneData::parentsIntoInternal(const UnsignedInt fieldId, const Containers: } void SceneData::parentsInto(const Containers::StridedArrayView1D& destination) const { - parentsIntoInternal(fieldFor(SceneField::Parent), destination); + const UnsignedInt fieldId = fieldFor(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::parentsInto(): field not found", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::parentsInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + parentsIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::parentsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::parentsInto(): field not found", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::parentsInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + parentsIntoInternal(fieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::parentsAsArray() const { @@ -747,7 +796,7 @@ Containers::Array SceneData::parentsAsArray() const { strings in the binary */ "Trade::SceneData::parentsInto(): field not found", {}); Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; - parentsIntoInternal(fieldId, out); + parentsIntoInternal(fieldId, 0, out); return out; } @@ -813,13 +862,16 @@ std::size_t SceneData::findTransformFields(UnsignedInt& transformationFieldId, U ~std::size_t{} : _fields[fieldToCheckForSize]._size; } -void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const { +void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* *FieldId, offset and destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + /** @todo apply scalings as well if dual complex? */ /* Prefer the transformation field, if present */ if(transformationFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[transformationFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1f = Containers::arrayCast<2, Float>(destination); if(field._fieldType == SceneFieldType::Matrix3x3) { @@ -846,7 +898,7 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi /* Apply scaling first, if present */ if(scalingFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[scalingFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); if(field._fieldType == SceneFieldType::Vector2) { applyScaling(fieldData, destination); @@ -861,7 +913,7 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi /* Apply rotation second, if present */ if(rotationFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[rotationFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); if(field._fieldType == SceneFieldType::Complex) { applyRotation(fieldData, destination); @@ -876,7 +928,7 @@ void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFi /* Apply translation last, if present */ if(translationFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[translationFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); if(field._fieldType == SceneFieldType::Vector2) { applyTranslation(fieldData, destination); @@ -902,7 +954,22 @@ void SceneData::transformations2DInto(const Containers::StridedArrayView1D& destination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + #ifndef CORRADE_NO_ASSERT + const std::size_t expectedSize = + #endif + findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::transformations2DInto(): no transformation-related field found", {}); + CORRADE_ASSERT(offset <= expectedSize, + "Trade::SceneData::transformations2DInto(): offset" << offset << "out of bounds for a field of size" << expectedSize, {}); + const std::size_t size = Math::min(destination.size(), expectedSize - offset); + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::transformations2DAsArray() const { @@ -913,17 +980,20 @@ Containers::Array SceneData::transformations2DAsArray() const { strings in the binary */ "Trade::SceneData::transformations2DInto(): no transformation-related field found", {}); Containers::Array out{NoInit, expectedSize}; - transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, out); + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, 0, out); return out; } -void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const { +void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* *FieldId, offset and destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + /** @todo apply scalings as well if dual quat? */ /* Prefer the transformation field, if present */ if(transformationFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[transformationFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1f = Containers::arrayCast<2, Float>(destination); if(field._fieldType == SceneFieldType::Matrix4x4) { @@ -950,7 +1020,7 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi /* Apply scaling first, if present */ if(scalingFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[scalingFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); if(field._fieldType == SceneFieldType::Vector3) { applyScaling(fieldData, destination); @@ -965,7 +1035,7 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi /* Apply rotation second, if present */ if(rotationFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[rotationFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); if(field._fieldType == SceneFieldType::Quaternion) { applyRotation(fieldData, destination); @@ -980,7 +1050,7 @@ void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFi /* Apply translation last, if present */ if(translationFieldId != ~UnsignedInt{}) { const SceneFieldData& field = _fields[translationFieldId]; - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); if(field._fieldType == SceneFieldType::Vector3) { applyTranslation(fieldData, destination); @@ -1006,7 +1076,22 @@ void SceneData::transformations3DInto(const Containers::StridedArrayView1D& destination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + #ifndef CORRADE_NO_ASSERT + const std::size_t expectedSize = + #endif + findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::transformations3DInto(): no transformation-related field found", {}); + CORRADE_ASSERT(offset <= expectedSize, + "Trade::SceneData::transformations3DInto(): offset" << offset << "out of bounds for a field of size" << expectedSize, {}); + const std::size_t size = Math::min(destination.size(), expectedSize - offset); + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::transformations3DAsArray() const { @@ -1017,22 +1102,16 @@ Containers::Array SceneData::transformations3DAsArray() const { strings in the binary */ "Trade::SceneData::transformations3DInto(): no transformation-related field found", {}); Containers::Array out{NoInit, expectedSize}; - transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, out); + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, 0, out); return out; } -void SceneData::indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - const char* const prefix, - #endif - const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const -{ - CORRADE_ASSERT(fieldId != ~UnsignedInt{}, - prefix << "field not found", ); +void SceneData::indexFieldIntoInternal(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 */ + const SceneFieldData& field = _fields[fieldId]; - CORRADE_ASSERT(destination.size() == field._size, - prefix << "expected a view with" << field._size << "elements but got" << destination.size(), ); - const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); if(field._fieldType == SceneFieldType::UnsignedInt) @@ -1044,111 +1123,155 @@ void SceneData::indexFieldIntoInternal( else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } -Containers::Array SceneData::indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT - const char* const prefix, - #endif - const SceneField name) const -{ - const UnsignedInt fieldId = fieldFor(name); - CORRADE_ASSERT(fieldId != ~UnsignedInt{}, prefix << "field not found", {}); +Containers::Array SceneData::indexFieldAsArrayInternal(const UnsignedInt fieldId) const { Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; - indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - prefix, - #endif - fieldId, out); + indexFieldIntoInternal(fieldId, 0, out); return out; } void SceneData::meshesInto(const Containers::StridedArrayView1D& destination) const { - indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - "Trade::SceneData::meshesInto():", - #endif - fieldFor(SceneField::Mesh), destination); + const UnsignedInt fieldId = fieldFor(SceneField::Mesh); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::meshesInto(): field not found", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::meshesInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + indexFieldIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::meshesInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::Mesh); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::meshesInto(): field not found", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::meshesInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + indexFieldIntoInternal(fieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::meshesAsArray() const { - return indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT + const UnsignedInt fieldId = fieldFor(SceneField::Mesh); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ - "Trade::SceneData::meshesInto():", - #endif - SceneField::Mesh); + "Trade::SceneData::meshesInto(): field not found", {}); + return indexFieldAsArrayInternal(fieldId); } void SceneData::meshMaterialsInto(const Containers::StridedArrayView1D& destination) const { - indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - "Trade::SceneData::meshMaterialsInto():", - #endif - fieldFor(SceneField::MeshMaterial), destination); + const UnsignedInt fieldId = fieldFor(SceneField::MeshMaterial); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::meshMaterialsInto(): field not found", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::meshMaterialsInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + indexFieldIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::meshMaterialsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::MeshMaterial); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::meshMaterialsInto(): field not found", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::meshMaterialsInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + indexFieldIntoInternal(fieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::meshMaterialsAsArray() const { - return indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT + const UnsignedInt fieldId = fieldFor(SceneField::MeshMaterial); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ - "Trade::SceneData::meshMaterialsInto():", - #endif - SceneField::MeshMaterial); + "Trade::SceneData::meshMaterialsInto(): field not found", {}); + return indexFieldAsArrayInternal(fieldId); } void SceneData::lightsInto(const Containers::StridedArrayView1D& destination) const { - indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - "Trade::SceneData::lightsInto():", - #endif - fieldFor(SceneField::Light), destination); + const UnsignedInt fieldId = fieldFor(SceneField::Light); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::lightsInto(): field not found", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::lightsInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + indexFieldIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::lightsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::Light); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::lightsInto(): field not found", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::lightsInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + indexFieldIntoInternal(fieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::lightsAsArray() const { - return indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT + const UnsignedInt fieldId = fieldFor(SceneField::Light); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ - "Trade::SceneData::lightsInto():", - #endif - SceneField::Light); + "Trade::SceneData::lightsInto(): field not found", {}); + return indexFieldAsArrayInternal(fieldId); } void SceneData::camerasInto(const Containers::StridedArrayView1D& destination) const { - indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - "Trade::SceneData::camerasInto():", - #endif - fieldFor(SceneField::Camera), destination); + const UnsignedInt fieldId = fieldFor(SceneField::Camera); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::camerasInto(): field not found", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::camerasInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + indexFieldIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::camerasInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::Camera); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::camerasInto(): field not found", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::camerasInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + indexFieldIntoInternal(fieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::camerasAsArray() const { - return indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT + const UnsignedInt fieldId = fieldFor(SceneField::Camera); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ - "Trade::SceneData::camerasInto():", - #endif - SceneField::Camera); + "Trade::SceneData::camerasInto(): field not found", {}); + return indexFieldAsArrayInternal(fieldId); } void SceneData::skinsInto(const Containers::StridedArrayView1D& destination) const { - indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - "Trade::SceneData::skinsInto():", - #endif - fieldFor(SceneField::Skin), destination); + const UnsignedInt fieldId = fieldFor(SceneField::Skin); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::skinsInto(): field not found", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::skinsInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + indexFieldIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::skinsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::Skin); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::skinsInto(): field not found", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::skinsInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + indexFieldIntoInternal(fieldId, offset, destination.prefix(size)); + return size; } Containers::Array SceneData::skinsAsArray() const { - return indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT + const UnsignedInt fieldId = fieldFor(SceneField::Skin); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ - "Trade::SceneData::skinsInto():", - #endif - SceneField::Skin); + "Trade::SceneData::skinsInto(): field not found", {}); + return indexFieldAsArrayInternal(fieldId); } Containers::Array SceneData::releaseFieldData() { diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 04e4ca4e3..84ac44e7a 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -1264,6 +1264,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void objectsInto(UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of object mapping for given field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref objectsInto(UnsignedInt, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the object mapping defined by @p offset + * and size of the @p destination view, returning the count of items + * actually extracted. The @p offset is expected to not be larger than + * the field size. + * @see @ref fieldSize(UnsignedInt) const + */ + std::size_t objectsInto(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Object mapping for given named field as 32-bit integers * @m_since_latest @@ -1293,6 +1306,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void objectsInto(SceneField fieldName, const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of object mapping for given named field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref objectsInto(SceneField, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the object mapping defined by @p offset + * and size of the @p destination view, returning the count of items + * actually extracted. The @p offset is expected to not be larger than + * the field size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t objectsInto(SceneField fieldName, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Parent indices as 32-bit integers * @m_since_latest @@ -1321,6 +1347,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void parentsInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of parent indices as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref parentsInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t parentsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief 2D transformations as 3x3 float matrices * @m_since_latest @@ -1351,6 +1390,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void transformations2DInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of 2D transformations as 3x3 float matrices into a pre-allocated view + * @m_since_latest + * + * Compared to @ref transformations2DInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t transformations2DInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief 3D transformations as 4x4 float matrices * @m_since_latest @@ -1381,6 +1433,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void transformations3DInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of 3D transformations as 4x4 float matrices into a pre-allocated view + * @m_since_latest + * + * Compared to @ref transformations3DInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t transformations3DInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Mesh IDs as 32-bit integers * @m_since_latest @@ -1404,6 +1469,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void meshesInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of mesh IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref meshesInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t meshesInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Mesh material IDs as 32-bit integers * @m_since_latest @@ -1427,6 +1505,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void meshMaterialsInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of mesh material IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref meshMaterialsInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t meshMaterialsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Light IDs as 32-bit integers * @m_since_latest @@ -1450,6 +1541,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void lightsInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of light IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref lightsInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t lightsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Camera IDs as 32-bit integers * @m_since_latest @@ -1473,6 +1577,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void camerasInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of camera IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref camerasInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t camerasInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Skin IDs as 32-bit integers * @m_since_latest @@ -1496,6 +1613,19 @@ class MAGNUM_TRADE_EXPORT SceneData { */ void skinsInto(const Containers::StridedArrayView1D& destination) const; + /** + * @brief A subrange of skin IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref skinsInto(const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the @p destination view, returning the count of items actually + * extracted. The @p offset is expected to not be larger than the field + * size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t skinsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** * @brief Release field data storage * @m_since_latest @@ -1542,28 +1672,25 @@ class MAGNUM_TRADE_EXPORT SceneData { /* Internal helper that doesn't assert, unlike fieldId() */ UnsignedInt fieldFor(SceneField name) const; - /* Like objects() / field(), but returning just a 1D view */ + /* Like objects() / field(), but returning just a 1D view, sliced from + offset to offset + size. The parameterless overloads are equal to + offset = 0 and size = field.size(). */ + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataObjectViewInternal(const SceneFieldData& field, std::size_t offset, std::size_t size) const; MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataObjectViewInternal(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; #ifndef CORRADE_NO_ASSERT template bool checkFieldTypeCompatibility(const SceneFieldData& attribute, const char* prefix) const; #endif - MAGNUM_TRADE_LOCAL void parentsIntoInternal(UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void objectsIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void parentsIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; MAGNUM_TRADE_LOCAL std::size_t findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const; - MAGNUM_TRADE_LOCAL void transformations2DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const; - MAGNUM_TRADE_LOCAL void transformations3DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const; - MAGNUM_TRADE_LOCAL void indexFieldIntoInternal( - #ifndef CORRADE_NO_ASSERT - const char* const prefix, - #endif - const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const; - MAGNUM_TRADE_LOCAL Containers::Array indexFieldAsArrayInternal( - #ifndef CORRADE_NO_ASSERT - const char* const prefix, - #endif - const SceneField name) const; + MAGNUM_TRADE_LOCAL void transformations2DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void transformations3DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void indexFieldIntoInternal(const UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL Containers::Array indexFieldAsArrayInternal(const UnsignedInt fieldId) const; DataFlags _dataFlags; SceneObjectType _objectType; diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index 1cb1e6254..c9630c62d 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -101,32 +101,44 @@ struct SceneDataTest: TestSuite::Tester { template void objectsAsArrayByIndex(); template void objectsAsArrayByName(); void objectsAsArrayLongType(); - void objectsIntoArrayInvalidSize(); + void objectsIntoArrayByIndex(); + void objectsIntoArrayByName(); + void objectsIntoArrayInvalidSizeOrOffset(); template void parentsAsArray(); #ifndef CORRADE_TARGET_32BIT void parentsAsArrayLongType(); #endif - void parentsIntoArrayInvalidSize(); + void parentsIntoArray(); + void parentsIntoArrayInvalidSizeOrOffset(); template void transformations2DAsArray(); template void transformations2DAsArrayTRS(); template void transformations2DAsArrayBut3DType(); template void transformations2DAsArrayBut3DTypeTRS(); - void transformations2DIntoArrayInvalidSize(); + void transformations2DIntoArray(); + void transformations2DIntoArrayTRS(); + void transformations2DIntoArrayInvalidSizeOrOffset(); template void transformations3DAsArray(); template void transformations3DAsArrayTRS(); template void transformations3DAsArrayBut2DType(); template void transformations3DAsArrayBut2DTypeTRS(); - void transformations3DIntoArrayInvalidSize(); + void transformations3DIntoArray(); + void transformations3DIntoArrayTRS(); + void transformations3DIntoArrayInvalidSizeOrOffset(); template void meshesAsArray(); - void meshesIntoArrayInvalidSize(); + void meshesIntoArray(); + void meshesIntoArrayInvalidSizeOrOffset(); template void meshMaterialsAsArray(); - void meshMaterialsIntoArrayInvalidSize(); + void meshMaterialsIntoArray(); + void meshMaterialsIntoArrayInvalidSizeOrOffset(); template void lightsAsArray(); - void lightsIntoArrayInvalidSize(); + void lightsIntoArray(); + void lightsIntoArrayInvalidSizeOrOffset(); template void camerasAsArray(); - void camerasIntoArrayInvalidSize(); + void camerasIntoArray(); + void camerasIntoArrayInvalidSizeOrOffset(); template void skinsAsArray(); - void skinsIntoArrayInvalidSize(); + void skinsIntoArray(); + void skinsIntoArrayInvalidSizeOrOffset(); void mutableAccessNotAllowed(); @@ -149,6 +161,18 @@ const struct { {"mutable", DataFlag::Mutable} }; +const struct { + const char* name; + std::size_t offset; + std::size_t size; + std::size_t expectedSize; +} IntoArrayOffsetData[]{ + {"whole", 0, 3, 3}, + {"one element in the middle", 1, 1, 1}, + {"suffix to a larger array", 2, 10, 1}, + {"offset at the end", 3, 10, 0} +}; + SceneDataTest::SceneDataTest() { addTests({&SceneDataTest::objectTypeSize, &SceneDataTest::objectTypeSizeInvalid, @@ -215,8 +239,13 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::objectsAsArrayByName, &SceneDataTest::objectsAsArrayByName, &SceneDataTest::objectsAsArrayByName, - &SceneDataTest::objectsAsArrayLongType, - &SceneDataTest::objectsIntoArrayInvalidSize, + &SceneDataTest::objectsAsArrayLongType}); + + addInstancedTests({&SceneDataTest::objectsIntoArrayByIndex, + &SceneDataTest::objectsIntoArrayByName}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::objectsIntoArrayInvalidSizeOrOffset, &SceneDataTest::parentsAsArray, &SceneDataTest::parentsAsArray, &SceneDataTest::parentsAsArray, @@ -224,7 +253,12 @@ SceneDataTest::SceneDataTest() { #ifndef CORRADE_TARGET_32BIT &SceneDataTest::parentsAsArrayLongType, #endif - &SceneDataTest::parentsIntoArrayInvalidSize, + }); + + addInstancedTests({&SceneDataTest::parentsIntoArray}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::parentsIntoArrayInvalidSizeOrOffset, &SceneDataTest::transformations2DAsArray, &SceneDataTest::transformations2DAsArray, &SceneDataTest::transformations2DAsArray, @@ -236,8 +270,13 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::transformations2DAsArrayBut3DType, &SceneDataTest::transformations2DAsArrayBut3DType, &SceneDataTest::transformations2DAsArrayBut3DTypeTRS, - &SceneDataTest::transformations2DAsArrayBut3DTypeTRS, - &SceneDataTest::transformations2DIntoArrayInvalidSize, + &SceneDataTest::transformations2DAsArrayBut3DTypeTRS}); + + addInstancedTests({&SceneDataTest::transformations2DIntoArray, + &SceneDataTest::transformations2DIntoArrayTRS}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffset, &SceneDataTest::transformations3DAsArray, &SceneDataTest::transformations3DAsArray, &SceneDataTest::transformations3DAsArray, @@ -249,28 +288,53 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::transformations3DAsArrayBut2DType, &SceneDataTest::transformations3DAsArrayBut2DType, &SceneDataTest::transformations3DAsArrayBut2DTypeTRS, - &SceneDataTest::transformations3DAsArrayBut2DTypeTRS, - &SceneDataTest::transformations3DIntoArrayInvalidSize, + &SceneDataTest::transformations3DAsArrayBut2DTypeTRS}); + + addInstancedTests({&SceneDataTest::transformations3DIntoArray, + &SceneDataTest::transformations3DIntoArrayTRS}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffset, &SceneDataTest::meshesAsArray, &SceneDataTest::meshesAsArray, - &SceneDataTest::meshesAsArray, - &SceneDataTest::meshesIntoArrayInvalidSize, + &SceneDataTest::meshesAsArray}); + + addInstancedTests({&SceneDataTest::meshesIntoArray}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::meshesIntoArrayInvalidSizeOrOffset, &SceneDataTest::meshMaterialsAsArray, &SceneDataTest::meshMaterialsAsArray, - &SceneDataTest::meshMaterialsAsArray, - &SceneDataTest::meshMaterialsIntoArrayInvalidSize, + &SceneDataTest::meshMaterialsAsArray}); + + addInstancedTests({&SceneDataTest::meshMaterialsIntoArray}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::meshMaterialsIntoArrayInvalidSizeOrOffset, &SceneDataTest::lightsAsArray, &SceneDataTest::lightsAsArray, - &SceneDataTest::lightsAsArray, - &SceneDataTest::lightsIntoArrayInvalidSize, + &SceneDataTest::lightsAsArray}); + + addInstancedTests({&SceneDataTest::lightsIntoArray}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::lightsIntoArrayInvalidSizeOrOffset, &SceneDataTest::camerasAsArray, &SceneDataTest::camerasAsArray, - &SceneDataTest::camerasAsArray, - &SceneDataTest::camerasIntoArrayInvalidSize, + &SceneDataTest::camerasAsArray}); + + addInstancedTests({&SceneDataTest::camerasIntoArray}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::camerasIntoArrayInvalidSizeOrOffset, &SceneDataTest::skinsAsArray, &SceneDataTest::skinsAsArray, - &SceneDataTest::skinsAsArray, - &SceneDataTest::skinsIntoArrayInvalidSize, + &SceneDataTest::skinsAsArray}); + + addInstancedTests({&SceneDataTest::skinsIntoArray}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::skinsIntoArrayInvalidSizeOrOffset, &SceneDataTest::mutableAccessNotAllowed, @@ -1675,6 +1739,7 @@ template void SceneDataTest::objectsAsArrayByIndex() { SceneFieldData{SceneField::Parent, Implementation::sceneObjectTypeFor(), nullptr, SceneFieldType::Int, nullptr}, SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} }}; + CORRADE_COMPARE_AS(scene.objectsAsArray(1), Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); @@ -1700,17 +1765,8 @@ template void SceneDataTest::objectsAsArrayByName() { SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} }}; - UnsignedInt expected[]{15, 37, 44}; - CORRADE_COMPARE_AS(arrayView(scene.objectsAsArray(SceneField::Mesh)), - Containers::arrayView(expected), - TestSuite::Compare::Container); - - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - UnsignedInt out[3]; - scene.objectsInto(SceneField::Mesh, out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.objectsAsArray(SceneField::Mesh), + Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); } @@ -1741,7 +1797,99 @@ void SceneDataTest::objectsAsArrayLongType() { "Trade::SceneData::objectsInto(): indices for up to 4294967296 objects can't fit into a 32-bit type, access them directly via objects() instead\n"); } -void SceneDataTest::objectsIntoArrayInvalidSize() { +void SceneDataTest::objectsIntoArrayByIndex() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[] { + {15, 0}, + {37, 1}, + {44, 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.objectsInto(1, out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::object), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.objectsInto(1, data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::objectsIntoArrayByName() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[] { + {15, 0}, + {37, 1}, + {44, 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.objectsInto(SceneField::Mesh, out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::object), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.objectsInto(SceneField::Mesh, data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::objectsIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -1762,9 +1910,13 @@ void SceneDataTest::objectsIntoArrayInvalidSize() { UnsignedInt destination[2]; scene.objectsInto(0, destination); scene.objectsInto(SceneField::Mesh, destination); + scene.objectsInto(0, 4, destination); + scene.objectsInto(SceneField::Mesh, 4, destination); CORRADE_COMPARE(out.str(), "Trade::SceneData::objectsInto(): expected a view with 3 elements but got 2\n" - "Trade::SceneData::objectsInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::objectsInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::objectsInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::objectsInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::parentsAsArray() { @@ -1787,17 +1939,8 @@ template void SceneDataTest::parentsAsArray() { SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} }}; - Int expected[]{15, -1, 44}; - CORRADE_COMPARE_AS(arrayView(scene.parentsAsArray()), - Containers::arrayView(expected), - TestSuite::Compare::Container); - - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - Int out[3]; - scene.parentsInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.parentsAsArray(), + Containers::arrayView({15, -1, 44}), TestSuite::Compare::Container); } @@ -1829,7 +1972,53 @@ void SceneDataTest::parentsAsArrayLongType() { } #endif -void SceneDataTest::parentsIntoArrayInvalidSize() { +void SceneDataTest::parentsIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Int parent; + } fields[] { + {1, 15}, + {0, -1}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}, + SceneFieldData{SceneField::Parent, + view.slice(&Field::object), + view.slice(&Field::parent)}, + }}; + + /* The offset-less overload should give back all data */ + { + Int out[3]; + scene.parentsInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::parent), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.parentsInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::parent) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::parentsIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -1849,8 +2038,10 @@ void SceneDataTest::parentsIntoArrayInvalidSize() { Error redirectError{&out}; Int destination[2]; scene.parentsInto(destination); + scene.parentsInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::parentsInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::parentsInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::parentsInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::transformations2DAsArray() { @@ -1908,23 +2099,12 @@ template void SceneDataTest::transformations2DAsArray() { components.slice(&Component::scaling)}, }}; - Matrix3 expected[]{ + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ Matrix3::translation({3.0f, 2.0f}), Matrix3::rotation(35.0_degf), Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation(-15.0_degf), Matrix3::rotation(-15.0_degf)*Matrix3::translation({1.5f, 2.5f}) - }; - CORRADE_COMPARE_AS(arrayView(scene.transformations2DAsArray()), - Containers::arrayView(expected), - TestSuite::Compare::Container); - - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - Matrix3 out[4]; - scene.transformations2DInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } template void SceneDataTest::transformations2DAsArrayTRS() { @@ -2103,7 +2283,112 @@ template void SceneDataTest::transformations2DAsArrayBut3DTypeTRS() { "Trade::SceneData::transformations2DInto(): field has a 3D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); } -void SceneDataTest::transformations2DIntoArrayInvalidSize() { +void SceneDataTest::transformations2DIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[] { + {1, Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {4, Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + view.slice(&Field::object), + view.slice(&Field::transformation)}, + }}; + + /* The offset-less overload should give back all data */ + { + Matrix3 out[3]; + scene.transformations2DInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::transformation), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.transformations2DInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::transformation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations2DIntoArrayTRS() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Vector2 translation; + Complex rotation; + Vector2 scaling; + } fields[] { + {1, {3.0f, 2.0f}, {}, {1.5f, 2.0f}}, + {0, {}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {4, {3.0f, 2.0f}, Complex::rotation(35.0_degf), {1.0f, 1.0f}} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}, + }}; + + Matrix3 expected[]{ + Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f}), + Matrix3::rotation(35.0_degf), + Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf) + }; + + /* The offset-less overload should give back all data */ + { + Matrix3 out[3]; + scene.transformations2DInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.transformations2DInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + Containers::arrayView(expected).slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2123,8 +2408,10 @@ void SceneDataTest::transformations2DIntoArrayInvalidSize() { Error redirectError{&out}; Matrix3 destination[2]; scene.transformations2DInto(destination); + scene.transformations2DInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::transformations2DInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::transformations2DInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::transformations2DInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::transformations3DAsArray() { @@ -2185,23 +2472,12 @@ template void SceneDataTest::transformations3DAsArray() { components.slice(&Component::scaling)}, }}; - Matrix4 expected[]{ + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ Matrix4::translation({3.0f, 2.0f, -0.5f}), Matrix4::rotationY(35.0_degf), Matrix4::translation({1.5f, 2.5f, 0.75f})*Matrix4::rotationX(-15.0_degf), Matrix4::rotationX(-15.0_degf)*Matrix4::translation({1.5f, 2.5f, 0.75f}) - }; - CORRADE_COMPARE_AS(arrayView(scene.transformations3DAsArray()), - Containers::arrayView(expected), - TestSuite::Compare::Container); - - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - Matrix4 out[4]; - scene.transformations3DInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } template void SceneDataTest::transformations3DAsArrayTRS() { @@ -2380,7 +2656,112 @@ template void SceneDataTest::transformations3DAsArrayBut2DTypeTRS() { "Trade::SceneData::transformations3DInto(): field has a 2D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); } -void SceneDataTest::transformations3DIntoArrayInvalidSize() { +void SceneDataTest::transformations3DIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[] { + {1, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})}, + {0, Matrix4::rotationX(35.0_degf)}, + {4, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + view.slice(&Field::object), + view.slice(&Field::transformation)}, + }}; + + /* The offset-less overload should give back all data */ + { + Matrix4 out[3]; + scene.transformations3DInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::transformation), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.transformations3DInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::transformation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations3DIntoArrayTRS() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Vector3 translation; + Quaternion rotation; + Vector3 scaling; + } fields[] { + {1, {3.0f, 2.0f, 1.0f}, {}, {1.5f, 2.0f, 4.5f}}, + {0, {}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {4, {3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}, + }}; + + Matrix4 expected[]{ + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f}), + Matrix4::rotationX(35.0_degf), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf) + }; + + /* The offset-less overload should give back all data */ + { + Matrix4 out[3]; + scene.transformations3DInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.transformations3DInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + Containers::arrayView(expected).slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2400,8 +2781,10 @@ void SceneDataTest::transformations3DIntoArrayInvalidSize() { Error redirectError{&out}; Matrix4 destination[2]; scene.transformations3DInto(destination); + scene.transformations3DInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::transformations3DInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::transformations3DInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::transformations3DInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::meshesAsArray() { @@ -2424,21 +2807,58 @@ template void SceneDataTest::meshesAsArray() { SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} }}; - UnsignedInt expected[]{15, 37, 44}; - CORRADE_COMPARE_AS(arrayView(scene.meshesAsArray()), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.meshesAsArray(), + Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); +} - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - UnsignedInt out[3]; - scene.meshesInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); +void SceneDataTest::meshesIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[]{ + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.meshesInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::mesh), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.meshesInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::mesh) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } } -void SceneDataTest::meshesIntoArrayInvalidSize() { +void SceneDataTest::meshesIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2458,8 +2878,10 @@ void SceneDataTest::meshesIntoArrayInvalidSize() { Error redirectError{&out}; UnsignedInt destination[2]; scene.meshesInto(destination); + scene.meshesInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::meshesInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::meshesInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::meshesInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::meshMaterialsAsArray() { @@ -2482,21 +2904,58 @@ template void SceneDataTest::meshMaterialsAsArray() { SceneFieldData{SceneField::MeshMaterial, view.slice(&Field::object), view.slice(&Field::meshMaterial)} }}; - UnsignedInt expected[]{15, 37, 44}; - CORRADE_COMPARE_AS(arrayView(scene.meshMaterialsAsArray()), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.meshMaterialsAsArray(), + Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); +} - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - UnsignedInt out[3]; - scene.meshMaterialsInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); +void SceneDataTest::meshMaterialsIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt meshMaterial; + } fields[]{ + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::MeshMaterial, + view.slice(&Field::object), + view.slice(&Field::meshMaterial)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.meshMaterialsInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::meshMaterial), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.meshMaterialsInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::meshMaterial) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } } -void SceneDataTest::meshMaterialsIntoArrayInvalidSize() { +void SceneDataTest::meshMaterialsIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2516,8 +2975,10 @@ void SceneDataTest::meshMaterialsIntoArrayInvalidSize() { Error redirectError{&out}; UnsignedInt destination[2]; scene.meshMaterialsInto(destination); + scene.meshMaterialsInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::meshMaterialsInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::meshMaterialsInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::meshMaterialsInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::lightsAsArray() { @@ -2540,21 +3001,58 @@ template void SceneDataTest::lightsAsArray() { SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} }}; - UnsignedInt expected[]{15, 37, 44}; - CORRADE_COMPARE_AS(arrayView(scene.lightsAsArray()), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.lightsAsArray(), + Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); +} - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - UnsignedInt out[3]; - scene.lightsInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); +void SceneDataTest::lightsIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt light; + } fields[] { + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Light, + view.slice(&Field::object), + view.slice(&Field::light)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.lightsInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::light), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.lightsInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::light) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } } -void SceneDataTest::lightsIntoArrayInvalidSize() { +void SceneDataTest::lightsIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2574,8 +3072,10 @@ void SceneDataTest::lightsIntoArrayInvalidSize() { Error redirectError{&out}; UnsignedInt destination[2]; scene.lightsInto(destination); + scene.lightsInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::lightsInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::lightsInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::lightsInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::camerasAsArray() { @@ -2598,21 +3098,58 @@ template void SceneDataTest::camerasAsArray() { SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} }}; - UnsignedInt expected[]{15, 37, 44}; - CORRADE_COMPARE_AS(arrayView(scene.camerasAsArray()), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.camerasAsArray(), + Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); +} - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - UnsignedInt out[3]; - scene.camerasInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); +void SceneDataTest::camerasIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt camera; + } fields[]{ + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Camera, + view.slice(&Field::object), + view.slice(&Field::camera)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.camerasInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::camera), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.camerasInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::camera) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } } -void SceneDataTest::camerasIntoArrayInvalidSize() { +void SceneDataTest::camerasIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2632,8 +3169,10 @@ void SceneDataTest::camerasIntoArrayInvalidSize() { Error redirectError{&out}; UnsignedInt destination[2]; scene.camerasInto(destination); + scene.camerasInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::camerasInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::camerasInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::camerasInto(): offset 4 out of bounds for a field of size 3\n"); } template void SceneDataTest::skinsAsArray() { @@ -2656,21 +3195,58 @@ template void SceneDataTest::skinsAsArray() { SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} }}; - UnsignedInt expected[]{15, 37, 44}; - CORRADE_COMPARE_AS(arrayView(scene.skinsAsArray()), - Containers::arrayView(expected), + CORRADE_COMPARE_AS(scene.skinsAsArray(), + Containers::arrayView({15, 37, 44}), TestSuite::Compare::Container); +} - /* Test Into() as well as it only shares a common helper with AsArray() but - has different top-level code paths */ - UnsignedInt out[3]; - scene.skinsInto(out); - CORRADE_COMPARE_AS(Containers::arrayView(out), - Containers::arrayView(expected), - TestSuite::Compare::Container); +void SceneDataTest::skinsIntoArray() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt skin; + } fields[] { + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Skin, + view.slice(&Field::object), + view.slice(&Field::skin)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.skinsInto(out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::skin), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.skinsInto(data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::skin) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } } -void SceneDataTest::skinsIntoArrayInvalidSize() { +void SceneDataTest::skinsIntoArrayInvalidSizeOrOffset() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -2690,8 +3266,10 @@ void SceneDataTest::skinsIntoArrayInvalidSize() { Error redirectError{&out}; UnsignedInt destination[2]; scene.skinsInto(destination); + scene.skinsInto(4, destination); CORRADE_COMPARE(out.str(), - "Trade::SceneData::skinsInto(): expected a view with 3 elements but got 2\n"); + "Trade::SceneData::skinsInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::skinsInto(): offset 4 out of bounds for a field of size 3\n"); } void SceneDataTest::mutableAccessNotAllowed() { @@ -2862,18 +3440,29 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(sceneFieldCustom(666)); scene.parentsAsArray(); + scene.parentsInto(nullptr); + scene.parentsInto(0, nullptr); scene.transformations2DAsArray(); - scene.transformations3DAsArray(); - /* Test both AsArray() and Into() for transformations as they only share a - common helper but have different top-level code paths. They however have - the same assertion messages to save binary size a bit. */ scene.transformations2DInto(nullptr); + scene.transformations2DInto(0, nullptr); + scene.transformations3DAsArray(); scene.transformations3DInto(nullptr); + scene.transformations3DInto(0, nullptr); scene.meshesAsArray(); + scene.meshesInto(nullptr); + scene.meshesInto(0, nullptr); scene.meshMaterialsAsArray(); + scene.meshMaterialsInto(nullptr); + scene.meshMaterialsInto(0, nullptr); scene.lightsAsArray(); + scene.lightsInto(nullptr); + scene.lightsInto(0, nullptr); scene.camerasAsArray(); + scene.camerasInto(nullptr); + scene.camerasInto(0, nullptr); scene.skinsAsArray(); + scene.skinsInto(nullptr); + scene.skinsInto(0, nullptr); CORRADE_COMPARE(out.str(), "Trade::SceneData::fieldData(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldName(): index 2 out of range for 2 fields\n" @@ -2898,19 +3487,32 @@ 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" + /* 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. */ + "Trade::SceneData::parentsInto(): field not found\n" + "Trade::SceneData::parentsInto(): field not found\n" "Trade::SceneData::parentsInto(): field not found\n" - /* Test both AsArray() and Into() for transformations as they only - share a common helper but have different top-level code paths. They - however have the same assertion messages to save binary size a - bit. */ "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" - "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::meshesInto(): field not found\n" "Trade::SceneData::meshesInto(): field not found\n" + "Trade::SceneData::meshesInto(): field not found\n" + "Trade::SceneData::meshMaterialsInto(): field not found\n" "Trade::SceneData::meshMaterialsInto(): field not found\n" + "Trade::SceneData::meshMaterialsInto(): field not found\n" + "Trade::SceneData::lightsInto(): field not found\n" + "Trade::SceneData::lightsInto(): field not found\n" "Trade::SceneData::lightsInto(): field not found\n" "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::skinsInto(): field not found\n" + "Trade::SceneData::skinsInto(): field not found\n" "Trade::SceneData::skinsInto(): field not found\n"); }