diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp index b2dc17f9a..b33c4b394 100644 --- a/src/Magnum/Trade/MaterialData.cpp +++ b/src/Magnum/Trade/MaterialData.cpp @@ -262,6 +262,18 @@ const void* MaterialData::attribute(const MaterialAttribute name) const { return attribute(string); } +const void* MaterialData::tryAttribute(const Containers::StringView name) const { + const UnsignedInt id = attributeFor(name); + if(id == ~UnsignedInt{}) return nullptr; + return _data[id].value(); +} + +const void* MaterialData::tryAttribute(const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(string); +} + Containers::Array MaterialData::release() { return std::move(_data); } diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index 92fcfc554..d5ada0477 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -31,6 +31,7 @@ */ #include +#include #include #include "Magnum/Magnum.h" @@ -590,7 +591,11 @@ class MAGNUM_TRADE_EXPORT MaterialData { /** @brief Attribute count */ UnsignedInt attributeCount() const { return _data.size(); } - /** @brief Whether the material has given attribute */ + /** + * @brief Whether the material has given attribute + * + * @see @ref tryAttribute(), @ref attributeOr() + */ bool hasAttribute(Containers::StringView name) const; bool hasAttribute(MaterialAttribute name) const; /**< @overload */ @@ -643,7 +648,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * The @p name is expected to exist. Cast the pointer to a concrete * type based on @ref attributeType(). - * @see @ref hasAttribute() + * @see @ref hasAttribute(), @ref tryAttribute(), @ref attributeOr() */ const void* attribute(Containers::StringView name) const; const void* attribute(MaterialAttribute name) const; /**< @overload */ @@ -667,6 +672,40 @@ class MAGNUM_TRADE_EXPORT MaterialData { template T attribute(Containers::StringView name) const; template T attribute(MaterialAttribute name) const; /**< @overload */ + /** + * @brief Type-erased attribute value, if exists + * + * Compared to @ref attribute(Containers::StringView name) const, if + * @p name doesn't exist, returns @cpp nullptr @ce instead of + * asserting. Cast the pointer to a concrete type based on + * @ref attributeType(). + * @see @ref hasAttribute(), @ref attributeOr() + */ + const void* tryAttribute(Containers::StringView name) const; + const void* tryAttribute(MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of a named attribute, if exists + * + * Compared to @ref attribute(Containers::StringView name) const, if + * @p name doesn't exist, returns @ref Corrade::Containers::NullOpt + * instead of asserting. Expects that @p T corresponds to + * @ref attributeType(Containers::StringView) const for given @p name. + */ + template Containers::Optional tryAttribute(Containers::StringView name) const; + template Containers::Optional tryAttribute(MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of a named attribute or a default + * + * Compared to @ref attribute(Containers::StringView name) const, if + * @p name doesn't exist, returns @p defaultValue instead of asserting. + * Expects that @p T corresponds to + * @ref attributeType(Containers::StringView) const for given @p name. + */ + template T attributeOr(Containers::StringView name, const T& defaultValue) const; + template T attributeOr(MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + /** * @brief Release data storage * @@ -774,6 +813,30 @@ template T MaterialData::attribute(MaterialAttribute name) const { return attribute(string); } +template Containers::Optional MaterialData::tryAttribute(Containers::StringView name) const { + const UnsignedInt id = attributeFor(name); + if(id == ~UnsignedInt{}) return {}; + return attribute(id); +} + +template Containers::Optional MaterialData::tryAttribute(MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(string); +} + +template T MaterialData::attributeOr(Containers::StringView name, const T& defaultValue) const { + const UnsignedInt id = attributeFor(name); + if(id == ~UnsignedInt{}) return defaultValue; + return attribute(id); +} + +template T MaterialData::attributeOr(MaterialAttribute name, const T& defaultValue) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); + return attributeOr(string, defaultValue); +} + }} #endif diff --git a/src/Magnum/Trade/Test/MaterialDataTest.cpp b/src/Magnum/Trade/Test/MaterialDataTest.cpp index ea9461868..14260820a 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -73,6 +73,7 @@ class MaterialDataTest: public TestSuite::Tester { void constructCopy(); void constructMove(); + void accessOptional(); void accessOutOfBounds(); void accessInvalidAttributeName(); void accessNotFound(); @@ -161,6 +162,7 @@ MaterialDataTest::MaterialDataTest() { &MaterialDataTest::constructCopy, &MaterialDataTest::constructMove, + &MaterialDataTest::accessOptional, &MaterialDataTest::accessOutOfBounds, &MaterialDataTest::accessInvalidAttributeName, &MaterialDataTest::accessNotFound, @@ -666,6 +668,31 @@ void MaterialDataTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +void MaterialDataTest::accessOptional() { + MaterialData data{{ + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute("SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute(MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute("SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute(MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute("SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute(MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr("SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr(MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute("DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute("DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr("DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr(MaterialAttribute::DiffuseTexture, 5u), 5); +} + void MaterialDataTest::accessOutOfBounds() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -708,6 +735,12 @@ void MaterialDataTest::accessInvalidAttributeName() { data.attribute(MaterialAttribute(0xfefe)); data.attribute(MaterialAttribute(0x0)); data.attribute(MaterialAttribute(0xfefe)); + data.tryAttribute(MaterialAttribute(0x0)); + data.tryAttribute(MaterialAttribute(0xfefe)); + data.tryAttribute(MaterialAttribute(0x0)); + data.tryAttribute(MaterialAttribute(0xfefe)); + data.attributeOr(MaterialAttribute(0x0), 42); + data.attributeOr(MaterialAttribute(0xfefe), 42); CORRADE_COMPARE(out.str(), "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" @@ -718,7 +751,13 @@ void MaterialDataTest::accessInvalidAttributeName() { "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n"); + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0xfefe)\n"); } void MaterialDataTest::accessNotFound() { @@ -759,7 +798,17 @@ void MaterialDataTest::accessWrongType() { data.attribute(0); data.attribute(MaterialAttribute::DiffuseColor); data.attribute("DiffuseColor"); + data.tryAttribute(MaterialAttribute::DiffuseColor); + data.tryAttribute("DiffuseColor"); + data.attributeOr(MaterialAttribute::DiffuseColor, Color3{1.0f}); + data.attributeOr("DiffuseColor", Color3{1.0f}); CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + /* tryAttribute() and attributeOr() delegate to attribute() so the + assert is the same */ + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n");