Browse Source

Trade: allow storing strings in MaterialData.

Because We Can. No, actually, this will be used for upcoming material
layers, it's not a bloat. However, this means you can do things like

    Trade::MaterialData weDoCssHere{{}, {
        {"color", "navy"},
        {"highlight", "rgba(35, 255, 78, 0.75)"},
        {"dropShadow", "var(--shadow-color)"}
    }};
pull/459/head
Vladimír Vondruš 6 years ago
parent
commit
b424b9dc4b
  1. 50
      src/Magnum/Trade/MaterialData.cpp
  2. 110
      src/Magnum/Trade/MaterialData.h
  3. 121
      src/Magnum/Trade/Test/MaterialDataTest.cpp

50
src/Magnum/Trade/MaterialData.cpp

@ -104,12 +104,29 @@ std::size_t materialAttributeTypeSize(const MaterialAttributeType type) {
case MaterialAttributeType::Pointer:
case MaterialAttributeType::MutablePointer:
return sizeof(void*);
case MaterialAttributeType::String:
CORRADE_ASSERT_UNREACHABLE("Trade::materialAttributeTypeSize(): string size is unknown", {});
}
CORRADE_ASSERT_UNREACHABLE("Trade::materialAttributeTypeSize(): invalid type" << type, {});
}
MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const MaterialAttributeType type, const std::size_t size, const void* const value) noexcept: _data{} /* zero-initialized */ {
/* Special handling for strings */
if(type == MaterialAttributeType::String) {
const auto& stringValue = *static_cast<const Containers::StringView*>(value);
/* The 4 extra bytes are for a null byte after both the name and value,
a type and a string size */
CORRADE_ASSERT(name.size() + stringValue.size() + 4 <= Implementation::MaterialAttributeDataSize,
"Trade::MaterialAttributeData: name" << name << "and value" << stringValue << "too long, expected at most" << Implementation::MaterialAttributeDataSize - 4 << "bytes in total but got" << name.size() + stringValue.size(), );
_data.type = MaterialAttributeType::String;
std::memcpy(_data.data + 1, name.data(), name.size());
std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - stringValue.size() - 2, stringValue.data(), stringValue.size());
_data.data[Implementation::MaterialAttributeDataSize - 1] = stringValue.size();
return;
}
/* The 2 extra bytes are for a null byte after the name and a type */
CORRADE_ASSERT(name.size() + size + 2 <= Implementation::MaterialAttributeDataSize,
"Trade::MaterialAttributeData: name" << name << "too long, expected at most" << Implementation::MaterialAttributeDataSize - size - 2 << "bytes for" << type << "but got" << name.size(), );
@ -118,22 +135,38 @@ MaterialAttributeData::MaterialAttributeData(const Containers::StringView name,
std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - size, value, size);
}
MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const MaterialAttributeType type, const void* const value) noexcept: MaterialAttributeData{name, type, materialAttributeTypeSize(type), value} {}
MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const MaterialAttributeType type, const void* const value) noexcept: MaterialAttributeData{name, type, type == MaterialAttributeType::String ? ~std::size_t{} : materialAttributeTypeSize(type), value} {}
MaterialAttributeData::MaterialAttributeData(const MaterialAttribute name, const MaterialAttributeType type, const void* const value) noexcept {
CORRADE_ASSERT(UnsignedInt(name) - 1 < Containers::arraySize(AttributeMap),
"Trade::MaterialAttributeData: invalid name" << name, );
CORRADE_ASSERT(AttributeMap[UnsignedInt(name) - 1].type == type,
"Trade::MaterialAttributeData: expected" << AttributeMap[UnsignedInt(name) - 1].type << "for" << name << "but got" << type, );
/* No builtin string attributes yet */
CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType::String);
_data.type = type;
std::memcpy(_data.data + 1, AttributeMap[UnsignedInt(name) - 1].name.data(), AttributeMap[UnsignedInt(name) - 1].name.size());
std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - AttributeMap[UnsignedInt(name) - 1].size, value, AttributeMap[UnsignedInt(name) - 1].size);
}
const void* MaterialAttributeData::value() const {
if(_data.type == MaterialAttributeType::String)
return _data.s.nameValue + Implementation::MaterialAttributeDataSize - _data.s.size - 3;
return _data.data + Implementation::MaterialAttributeDataSize - materialAttributeTypeSize(_data.type);
}
#ifndef DOXYGEN_GENERATING_OUTPUT
/* On Windows (MSVC, clang-cl and MinGw) it needs an explicit export otherwise
the symbol doesn't get exported. */
template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialAttributeData::value<Containers::StringView>() const {
CORRADE_ASSERT(_data.type == MaterialAttributeType::String,
"Trade::MaterialAttributeData::value():" << (_data.data + 1) << "of" << _data.type << "can't be retrieved as a string", {});
return {_data.s.nameValue + Implementation::MaterialAttributeDataSize - _data.s.size - 3, _data.s.size, Containers::StringViewFlag::NullTerminated};
}
#endif
MaterialData::MaterialData(const MaterialTypes types, Containers::Array<MaterialAttributeData>&& data, const void* const importerState) noexcept: _data{std::move(data)}, _types{types}, _importerState{importerState} {
#ifndef CORRADE_NO_ASSERT
/* Not checking what's already done in MaterialAttributeData constructor.
@ -270,6 +303,20 @@ const void* MaterialData::attribute(const MaterialAttribute name) const {
return attribute(string);
}
#ifndef DOXYGEN_GENERATING_OUTPUT
/* On Windows (MSVC, clang-cl and MinGw) it needs an explicit export otherwise
the symbol doesn't get exported. */
template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialData::attribute<Containers::StringView>(const UnsignedInt id) const {
/* Can't delegate to attribute() returning const void* because that doesn't
include the size */
CORRADE_ASSERT(id < _data.size(),
"Trade::MaterialData::attribute(): index" << id << "out of range for" << _data.size() << "attributes", {});
CORRADE_ASSERT(_data[id]._data.type == MaterialAttributeType::String,
"Trade::MaterialData::attribute():" << (_data[id]._data.data + 1) << "of" << _data[id]._data.type << "can't be retrieved as a string", {});
return {_data[id]._data.s.nameValue + Implementation::MaterialAttributeDataSize - _data[id]._data.s.size - 3, _data[id]._data.s.size, Containers::StringViewFlag::NullTerminated};
}
#endif
const void* MaterialData::tryAttribute(const Containers::StringView name) const {
const UnsignedInt id = attributeFor(name);
if(id == ~UnsignedInt{}) return nullptr;
@ -355,6 +402,7 @@ Debug& operator<<(Debug& debug, const MaterialAttributeType value) {
_c(Matrix4x3)
_c(Pointer)
_c(MutablePointer)
_c(String)
#undef _c
/* LCOV_EXCL_STOP */
}

110
src/Magnum/Trade/MaterialData.h

@ -332,11 +332,21 @@ enum class MaterialAttributeType: UnsignedByte {
* `T` but the user has to ensure the type is correct.
*/
MutablePointer,
/**
* Null-terminated string. Can be stored using any type convertible to
* @ref Corrade::Containers::StringView, retrieval has to be done using
* @ref Corrade::Containers::StringView.
*/
String
};
/**
@brief Byte size of a material attribute type
@m_since_latest
Can't be used with @ref MaterialAttributeType::String, as the size varies
depending on the value.
*/
MAGNUM_TRADE_EXPORT std::size_t materialAttributeTypeSize(MaterialAttributeType type);
@ -382,11 +392,33 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
* don't need @cpp constexpr @ce, as it additionally checks that given
* attribute has the expected type.
*/
template<class T> constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, const T& value) noexcept;
template<class T
#ifndef DOXYGEN_GENERATING_OUTPUT
, class = typename std::enable_if<!std::is_convertible<const T&, Containers::StringView>::value>::type
#endif
> constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, const T& value) noexcept;
/**
* @brief Construct with a string name and value
* @param name Attribute name
* @param value Attribute value
*
* The combined length of @p name and @p value is expected to fit into
* 61 bytes. Type is set to @ref MaterialAttributeType::String.
*
* This function is useful in @cpp constexpr @ce contexts and for
* creating custom material attributes. For known attributes prefer to
* use @ref MaterialAttributeData(MaterialAttribute, const T&) if you
* don't need @cpp constexpr @ce, as it additionally checks that given
* attribute has the expected type.
*/
constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, Containers::StringView value) noexcept;
#ifndef DOXYGEN_GENERATING_OUTPUT
/* "Sure can't be constexpr" overload to avoid going through the
/* "Sure can't be constexpr" overloads to avoid going through the
*insane* overload puzzle when not needed */
template<class T> /*implicit*/ MaterialAttributeData(const char* name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor<T>::type(), sizeof(T), &value} {}
template<class T, class = typename std::enable_if<!std::is_convertible<const T&, Containers::StringView>::value>::type> /*implicit*/ MaterialAttributeData(const char* name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor<T>::type(), sizeof(T), &value} {}
/*implicit*/ MaterialAttributeData(const char* name, Containers::StringView value) noexcept: MaterialAttributeData{name, MaterialAttributeType::String, 0, &value} {}
#endif
/**
@ -402,7 +434,11 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
*
* @snippet MagnumTrade.cpp MaterialAttributeData-name
*/
template<class T> /*implicit*/ MaterialAttributeData(MaterialAttribute name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor<T>::type(), &value} {}
template<class T
#ifndef DOXYGEN_GENERATING_OUTPUT
, class = typename std::enable_if<!std::is_convertible<const T&, Containers::StringView>::value>::type
#endif
> /*implicit*/ MaterialAttributeData(MaterialAttribute name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor<T>::type(), &value} {}
/**
* @brief Construct from a type-erased value
@ -410,9 +446,15 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
* @param type Attribute type
* @param value Type-erased value
*
* Copies a number of bytes according to @ref materialAttributeTypeSize()
* from @p value. The @p name together with @p value is expected to fit
* into 62 bytes.
* In case @p type is not @ref MaterialAttributeType::String, copies a
* number of bytes according to @ref materialAttributeTypeSize() from
* @p value. The @p name together with @p value is expected to fit into
* 62 bytes.
*
* In case @p type is @ref MaterialAttributeType::String, @p value is
* expected to point to a @ref Containers::StringView. The combined
* length of @p name and @p value strings is expected to fit into 61
* bytes.
*/
/*implicit*/ MaterialAttributeData(Containers::StringView name, MaterialAttributeType type, const void* value) noexcept;
@ -447,6 +489,12 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
* in case of a @ref MaterialAttributeType::Pointer or a
* @ref MaterialAttributeType::MutablePointer, returns a
* *pointer to a pointer*, not the pointer value itself.
*
* In case of a @ref MaterialAttributeType::String, returns a
* null-terminated @cpp const char* @ce (not a pointer to
* @ref Containers::StringView). This doesn't preserve the actual
* string size in case the string data contain zero bytes, thus prefer
* to use typed access in that case.
*/
const void* value() const;
@ -484,6 +532,14 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
lookup fast and efficient, and data being at the end (instead of
right after the null-terminated string) makes them accessible in O(1)
as well. */
struct StringData {
template<std::size_t ...sequence> constexpr explicit StringData(MaterialAttributeType type, Containers::StringView name, Containers::StringView value, Math::Implementation::Sequence<sequence...>): type{type}, nameValue{(sequence < name.size() ? name[sequence] : (sequence - (Implementation::MaterialAttributeDataSize - value.size() - 3) < value.size() ? value[sequence - (Implementation::MaterialAttributeDataSize - value.size() - 3)] : '\0'))...}, size{UnsignedByte(value.size())} {}
constexpr explicit StringData(MaterialAttributeType type, Containers::StringView name, Containers::StringView value): StringData{type, name, value, typename Math::Implementation::GenerateSequence<Implementation::MaterialAttributeDataSize - 2>::Type{}} {}
MaterialAttributeType type;
char nameValue[Implementation::MaterialAttributeDataSize - 2];
UnsignedByte size;
};
union ErasedScalar {
constexpr explicit ErasedScalar(Float value): f{value} {}
constexpr explicit ErasedScalar(Deg value): f{Float(value)} {}
@ -529,6 +585,8 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
union CORRADE_ALIGNAS(8) Storage {
constexpr explicit Storage() noexcept: data{} {}
constexpr explicit Storage(Containers::StringView name, Containers::StringView value) noexcept: s{MaterialAttributeType::String, name, value} {}
template<class T> constexpr explicit Storage(typename std::enable_if<sizeof(T) == 1, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _1{type, name, value} {}
template<class T> constexpr explicit Storage(typename std::enable_if<sizeof(T) == 4 && !std::is_pointer<T>::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _4{type, name, value} {}
template<class T> constexpr explicit Storage(typename std::enable_if<sizeof(T) == 8 && !Math::IsVector<T>::value && !std::is_pointer<T>::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _8{type, name, value} {}
@ -544,6 +602,7 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData {
MaterialAttributeType type;
char data[Implementation::MaterialAttributeDataSize];
StringData s;
Data<bool> _1;
Data<const void*> p;
Data<ErasedScalar> _4;
@ -810,6 +869,12 @@ class MAGNUM_TRADE_EXPORT MaterialData {
* in case of a @ref MaterialAttributeType::Pointer or a
* @ref MaterialAttributeType::MutablePointer, returns a
* *pointer to a pointer*, not the pointer value itself.
*
* In case of a @ref MaterialAttributeType::String returns a
* null-terminated @cpp const char* @ce (not a pointer to
* @ref Containers::StringView). This doesn't preserve the actual
* string size in case the string data contain zero bytes, thus prefer
* to use typed access in that case.
*/
const void* attribute(UnsignedInt id) const;
@ -821,6 +886,12 @@ class MAGNUM_TRADE_EXPORT MaterialData {
* in case of a @ref MaterialAttributeType::Pointer or a
* @ref MaterialAttributeType::MutablePointer, returns a
* *pointer to a pointer*, not the pointer value itself.
*
* In case of a @ref MaterialAttributeType::String returns a
* null-terminated @cpp const char* @ce (not a pointer to
* @ref Containers::StringView). This doesn't preserve the actual
* string size in case the string data contain zero bytes, thus prefer
* to use typed access in that case.
* @see @ref hasAttribute(), @ref tryAttribute(), @ref attributeOr()
*/
const void* attribute(Containers::StringView name) const;
@ -831,7 +902,8 @@ class MAGNUM_TRADE_EXPORT MaterialData {
*
* The @p id is expected to be smaller than @ref attributeCount() const.
* Expects that @p T corresponds to @ref attributeType(UnsignedInt) const
* for given @p id.
* for given @p id. In case of a string, the returned view always has
* @ref Corrade::Containers::StringViewFlag::NullTerminated set.
*/
template<class T> T attribute(UnsignedInt id) const;
@ -840,6 +912,8 @@ class MAGNUM_TRADE_EXPORT MaterialData {
*
* The @p name is expected to exist. Expects that @p T corresponds to
* @ref attributeType(Containers::StringView) const for given @p name.
* In case of a string, the returned view always has
* @ref Corrade::Containers::StringViewFlag::NullTerminated set.
* @see @ref hasAttribute()
*/
template<class T> T attribute(Containers::StringView name) const;
@ -996,6 +1070,8 @@ namespace Implementation {
return MaterialAttributeType::MutablePointer;
}
};
/* No specialization for StringView as this type trait should not be used
in that case */
#ifndef DOXYGEN_GENERATING_OUTPUT
#define _c(type_) template<> struct MaterialAttributeTypeFor<type_> { \
constexpr static MaterialAttributeType type() { \
@ -1035,7 +1111,15 @@ namespace Implementation {
}
/* The 2 extra bytes are for a null byte after the name and a type */
template<class T> constexpr MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const T& value) noexcept: _data{Implementation::MaterialAttributeTypeFor<T>::type(), (CORRADE_CONSTEXPR_ASSERT(name.size() + sizeof(T) + 2 <= Implementation::MaterialAttributeDataSize, "Trade::MaterialAttributeData: name" << name << "too long, expected at most" << Implementation::MaterialAttributeDataSize - sizeof(T) - 2 << "bytes for" << Implementation::MaterialAttributeTypeFor<T>::type() << "but got" << name.size()), name), value} {}
template<class T
#ifndef DOXYGEN_GENERATING_OUTPUT
, class
#endif
> constexpr MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const T& value) noexcept: _data{Implementation::MaterialAttributeTypeFor<T>::type(), (CORRADE_CONSTEXPR_ASSERT(name.size() + sizeof(T) + 2 <= Implementation::MaterialAttributeDataSize, "Trade::MaterialAttributeData: name" << name << "too long, expected at most" << Implementation::MaterialAttributeDataSize - sizeof(T) - 2 << "bytes for" << Implementation::MaterialAttributeTypeFor<T>::type() << "but got" << name.size()), name), value} {}
/* The 4 extra bytes are for a null byte after both the name and value, a type
and a string size */
constexpr MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const Containers::StringView value) noexcept: _data{(CORRADE_CONSTEXPR_ASSERT(name.size() + value.size() + 4 <= Implementation::MaterialAttributeDataSize, "Trade::MaterialAttributeData: name" << name << "and value" << value << "too long, expected at most" << Implementation::MaterialAttributeDataSize - 4 << "bytes in total but got" << name.size() + value.size()), name), value} {}
template<class T> T MaterialAttributeData::value() const {
CORRADE_ASSERT(Implementation::MaterialAttributeTypeFor<T>::type() == _data.type,
@ -1043,6 +1127,10 @@ template<class T> T MaterialAttributeData::value() const {
return *reinterpret_cast<const T*>(value());
}
#ifndef DOXYGEN_GENERATING_OUTPUT
template<> Containers::StringView MaterialAttributeData::value<Containers::StringView>() const;
#endif
template<class T> T MaterialData::attribute(UnsignedInt id) const {
const void* const value = attribute(id);
#ifdef CORRADE_GRACEFUL_ASSERT
@ -1053,6 +1141,10 @@ template<class T> T MaterialData::attribute(UnsignedInt id) const {
return *reinterpret_cast<const T*>(value);
}
#ifndef DOXYGEN_GENERATING_OUTPUT
template<> Containers::StringView MaterialData::attribute<Containers::StringView>(UnsignedInt) const;
#endif
template<class T> T MaterialData::attribute(Containers::StringView name) const {
const UnsignedInt id = attributeFor(name);
CORRADE_ASSERT(id != ~UnsignedInt{},

121
src/Magnum/Trade/Test/MaterialDataTest.cpp

@ -57,13 +57,16 @@ class MaterialDataTest: public TestSuite::Tester {
void constructAttributePointer();
void constructAttributeMutablePointer();
void constructAttributeStringValue();
void constructAttributeInvalidName();
void constructAttributeWrongTypeForName();
void constructAttributeInvalidType();
void constructAttributeTooLarge();
void constructAttributeTooLargeString();
void constructAttributeWrongAccessType();
void constructAttributeWrongAccessPointerType();
void constructAttributeWrongAccessTypeString();
void construct();
void constructEmptyAttribute();
@ -80,12 +83,14 @@ class MaterialDataTest: public TestSuite::Tester {
void access();
void accessPointer();
void accessString();
void accessOptional();
void accessOutOfBounds();
void accessInvalidAttributeName();
void accessNotFound();
void accessWrongType();
void accessWrongPointerType();
void accessWrongTypeString();
void release();
@ -162,13 +167,16 @@ MaterialDataTest::MaterialDataTest() {
&MaterialDataTest::constructAttributePointer,
&MaterialDataTest::constructAttributeMutablePointer,
&MaterialDataTest::constructAttributeStringValue,
&MaterialDataTest::constructAttributeInvalidName,
&MaterialDataTest::constructAttributeWrongTypeForName,
&MaterialDataTest::constructAttributeInvalidType,
&MaterialDataTest::constructAttributeTooLarge,
&MaterialDataTest::constructAttributeTooLargeString,
&MaterialDataTest::constructAttributeWrongAccessType,
&MaterialDataTest::constructAttributeWrongAccessPointerType,
&MaterialDataTest::constructAttributeWrongAccessTypeString,
&MaterialDataTest::construct,
&MaterialDataTest::constructEmptyAttribute});
@ -188,12 +196,14 @@ MaterialDataTest::MaterialDataTest() {
&MaterialDataTest::access,
&MaterialDataTest::accessPointer,
&MaterialDataTest::accessString,
&MaterialDataTest::accessOptional,
&MaterialDataTest::accessOutOfBounds,
&MaterialDataTest::accessInvalidAttributeName,
&MaterialDataTest::accessNotFound,
&MaterialDataTest::accessWrongType,
&MaterialDataTest::accessWrongPointerType,
&MaterialDataTest::accessWrongTypeString,
&MaterialDataTest::release,
@ -256,9 +266,11 @@ void MaterialDataTest::attributeTypeSizeInvalid() {
Error redirectError{&out};
materialAttributeTypeSize(MaterialAttributeType(0x0));
materialAttributeTypeSize(MaterialAttributeType(0xfe));
materialAttributeTypeSize(MaterialAttributeType::String);
CORRADE_COMPARE(out.str(),
"Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0x0)\n"
"Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0xfe)\n");
"Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0xfe)\n"
"Trade::materialAttributeTypeSize(): string size is unknown\n");
}
void MaterialDataTest::attributeMap() {
@ -439,6 +451,41 @@ void MaterialDataTest::constructAttributeMutablePointer() {
CORRADE_COMPARE(typeErased.value<void*>(), &data);
}
void MaterialDataTest::constructAttributeStringValue() {
/* Explicitly using a non-null-terminated view on input to check the null
byte isn't read by accident*/
MaterialAttributeData attribute{"name that's long", "and a value\0that's also long but still fits!!"_s.except(1)};
CORRADE_COMPARE(attribute.name(), "name that's long");
CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0');
CORRADE_COMPARE(attribute.type(), MaterialAttributeType::String);
/* Pointer access will stop at the first null byte, but typed access won't */
CORRADE_COMPARE(static_cast<const char*>(attribute.value()), "and a value"_s);
CORRADE_COMPARE(attribute.value<Containers::StringView>(), "and a value\0that's also long but still fits!"_s);
CORRADE_COMPARE(attribute.value<Containers::StringView>().flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(attribute.value<Containers::StringView>()[attribute.value<Containers::StringView>().size()], '\0');
constexpr MaterialAttributeData cattribute{"name that's long"_s, "and a value\0that's also long but still fits!!"_s.except(1)};
CORRADE_COMPARE(cattribute.name(), "name that's long");
CORRADE_COMPARE(cattribute.name().flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(cattribute.name()[cattribute.name().size()], '\0');
CORRADE_COMPARE(cattribute.type(), MaterialAttributeType::String);
CORRADE_COMPARE(cattribute.value<Containers::StringView>(), "and a value\0that's also long but still fits!"_s);
CORRADE_COMPARE(cattribute.value<Containers::StringView>().flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(cattribute.value<Containers::StringView>()[cattribute.value<Containers::StringView>().size()], '\0');
/* Type-erased variant */
const Containers::StringView value = "and a value\0that's also long but still fits!!"_s.except(1);
MaterialAttributeData typeErased{"name that's long", MaterialAttributeType::String, &value};
CORRADE_COMPARE(typeErased.name(), "name that's long");
CORRADE_COMPARE(typeErased.name().flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(typeErased.name()[typeErased.name().size()], '\0');
CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::String);
CORRADE_COMPARE(typeErased.value<Containers::StringView>(), "and a value\0that's also long but still fits!"_s);
CORRADE_COMPARE(typeErased.value<Containers::StringView>().flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(typeErased.value<Containers::StringView>()[typeErased.value<Containers::StringView>().size()], '\0');
}
void MaterialDataTest::constructAttributeInvalidName() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
@ -495,6 +542,22 @@ void MaterialDataTest::constructAttributeTooLarge() {
"Trade::MaterialAttributeData: name attributeIsLong too long, expected at most 14 bytes for Trade::MaterialAttributeType::Matrix3x4 but got 15\n");
}
void MaterialDataTest::constructAttributeTooLargeString() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
std::ostringstream out;
Error redirectError{&out};
MaterialAttributeData{"attribute is long", "This is a problem, got a long piece of text!"};
/* Constexpr variant has the same assert, but in the header. It should have
the same output. */
/*constexpr*/ MaterialAttributeData{"attribute is long"_s, "This is a problem, got a long piece of text!"_s};
CORRADE_COMPARE(out.str(),
"Trade::MaterialAttributeData: name attribute is long and value This is a problem, got a long piece of text! too long, expected at most 60 bytes in total but got 61\n"
"Trade::MaterialAttributeData: name attribute is long and value This is a problem, got a long piece of text! too long, expected at most 60 bytes in total but got 61\n");
}
void MaterialDataTest::constructAttributeWrongAccessType() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
@ -523,6 +586,17 @@ void MaterialDataTest::constructAttributeWrongAccessPointerType() {
"Trade::MaterialAttributeData::value(): improper type requested for boom of Trade::MaterialAttributeType::Pointer\n");
}
void MaterialDataTest::constructAttributeWrongAccessTypeString() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
std::ostringstream out;
Error redirectError{&out};
MaterialAttributeData{"thing3", Matrix4x3{}}.value<Containers::StringView>();
CORRADE_COMPARE(out.str(), "Trade::MaterialAttributeData::value(): thing3 of Trade::MaterialAttributeType::Matrix4x3 can't be retrieved as a string\n");
}
void MaterialDataTest::construct() {
int state;
MaterialData data{MaterialType::Phong, {
@ -818,6 +892,19 @@ void MaterialDataTest::accessPointer() {
CORRADE_COMPARE(data.attribute<Long*>("mutable"), &b);
}
void MaterialDataTest::accessString() {
MaterialData data{{}, {
{"name?", "THIS IS\0WHO I AM!"_s}
}};
CORRADE_COMPARE(data.attributeType("name?"), MaterialAttributeType::String);
/* Pointer access will stop at the first null byte, but typed access won't */
CORRADE_COMPARE(static_cast<const char*>(data.attribute(0)), "THIS IS"_s);
CORRADE_COMPARE(data.attribute<Containers::StringView>(0), "THIS IS\0WHO I AM!"_s);
CORRADE_COMPARE(data.attribute<Containers::StringView>(0).flags(), Containers::StringViewFlag::NullTerminated);
CORRADE_COMPARE(data.attribute<Containers::StringView>(0)[data.attribute<Containers::StringView>(0).size()], '\0');
}
void MaterialDataTest::accessOptional() {
MaterialData data{{}, {
{MaterialAttribute::AlphaMask, 0.5f},
@ -859,10 +946,12 @@ void MaterialDataTest::accessOutOfBounds() {
data.attributeType(2);
data.attribute(2);
data.attribute<Int>(2);
data.attribute<Containers::StringView>(2);
CORRADE_COMPARE(out.str(),
"Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes\n"
"Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes\n"
"Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n"
"Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n"
"Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n");
}
@ -990,6 +1079,36 @@ void MaterialDataTest::accessWrongPointerType() {
"Trade::MaterialData::attribute(): improper type requested for pointer of Trade::MaterialAttributeType::Pointer\n");
}
void MaterialDataTest::accessWrongTypeString() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
MaterialData data{{}, {
{"Shininess", 0.0f}
}};
std::ostringstream out;
Error redirectError{&out};
data.attribute<Containers::StringView>(0);
data.attribute<Containers::StringView>(MaterialAttribute::Shininess);
data.attribute<Containers::StringView>("Shininess");
data.tryAttribute<Containers::StringView>(MaterialAttribute::Shininess);
data.tryAttribute<Containers::StringView>("Shininess");
data.attributeOr(MaterialAttribute::Shininess, Containers::StringView{});
data.attributeOr("Shininess", Containers::StringView{});
CORRADE_COMPARE(out.str(),
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"
/* tryAttribute() and attributeOr() delegate to attribute() so the
assert is the same */
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"
"Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n");
}
void MaterialDataTest::release() {
MaterialData data{{}, {
{"DiffuseColor", 0xff3366aa_rgbaf},

Loading…
Cancel
Save