Skip to content

Commit

Permalink
--Add ConfigValue support for Mn::Matrix3 (3x3 matrices of floats) (#…
Browse files Browse the repository at this point in the history
…1753)

* --add support for 3x3 Magnum matrices in ConfigValue; matrix JSON test; streamline simple tests via template func
* --add double test to verify alignment; fix alignment for 32 bit systems like emscriten
  • Loading branch information
jturner65 authored May 17, 2022
1 parent 87c498f commit b0d9c1f
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 133 deletions.
10 changes: 9 additions & 1 deletion src/esp/bindings/ConfigBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ py::object getObjectForConfigValue(const ConfigValue& value) {
return py::cast(value.get<std::string>());
case ConfigStoredType::MagnumVec3:
return py::cast(value.get<Mn::Vector3>());
case ConfigStoredType::MagnumMat3:
return py::cast(value.get<Mn::Matrix3>());
case ConfigStoredType::MagnumQuat:
return py::cast(value.get<Mn::Quaternion>());
case ConfigStoredType::MagnumRad:
Expand All @@ -42,6 +44,7 @@ void initConfigBindings(py::module& m) {
.value("Float", ConfigStoredType::Double)
.value("String", ConfigStoredType::String)
.value("MagnumVec3", ConfigStoredType::MagnumVec3)
.value("MagnumMat3", ConfigStoredType::MagnumMat3)
.value("MagnumQuat", ConfigStoredType::MagnumQuat)
.value("MagnumRad", ConfigStoredType::MagnumRad);

Expand Down Expand Up @@ -101,7 +104,12 @@ void initConfigBindings(py::module& m) {
const Magnum::Vector3& val) { self.set(key, val); },
R"(Set the value specified by given string key to be specified Magnum::Vector3 value)",
"key"_a, "value"_a)

.def(
"set",
[](Configuration& self, const std::string& key,
const Magnum::Matrix3& val) { self.set(key, val); },
R"(Set the value specified by given string key to be specified Magnum::Matrix3 value)",
"key"_a, "value"_a)
.def(
"get_type", &Configuration::getType,
R"(Retrieves the ConfigStoredType of the value referred to by the passed key.)")
Expand Down
93 changes: 64 additions & 29 deletions src/esp/core/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ const std::unordered_map<ConfigStoredType, std::string, ConfigStoredTypeHash>
{ConfigStoredType::Boolean, "bool"},
{ConfigStoredType::Integer, "int"},
{ConfigStoredType::Double, "double"},
{ConfigStoredType::MagnumVec3, "Magnum::Vector3"},
{ConfigStoredType::MagnumQuat, "Magnum::Quaternion"},
{ConfigStoredType::MagnumRad, "Magnum::Rad"},
{ConfigStoredType::MagnumVec3, "Mn::Vector3"},
{ConfigStoredType::MagnumMat3, "Mn::Matrix3"},
{ConfigStoredType::MagnumQuat, "Mn::Quaternion"},
{ConfigStoredType::MagnumRad, "Mn::Rad"},
{ConfigStoredType::String, "std::string"}};

// force this functionality to remain local to this file.
Expand Down Expand Up @@ -86,7 +87,7 @@ constexpr NonTrivialTypeHandler nonTrivialTypeHandlers[]{
NonTrivialTypeHandler::make<std::string>()};

NonTrivialTypeHandler nonTrivialConfigStoredTypeHandlerFor(
ConfigStoredType type) { // eugh, long name
ConfigStoredType type) {
const std::size_t i = int(type) - int(ConfigStoredType::_nonTrivialTypes);
CORRADE_INTERNAL_ASSERT(i <
Cr::Containers::arraySize(nonTrivialTypeHandlers));
Expand Down Expand Up @@ -183,6 +184,17 @@ std::string ConfigValue::getAsString() const {
auto v = get<Mn::Vector3>();
return Cr::Utility::formatString("[{} {} {}]", v.x(), v.y(), v.z());
}
case ConfigStoredType::MagnumMat3: {
auto m = get<Mn::Matrix3>();
std::string res = "[";
for (int i = 0; i < m.Size; ++i) {
auto v = m.row(i);
Cr::Utility::formatInto(res, res.length(), "[{} {} {}]", v.x(), v.y(),
v.z());
}
Cr::Utility::formatInto(res, res.length(), "]");
return res;
}
case ConfigStoredType::MagnumQuat: {
auto q = get<Mn::Quaternion>();
auto qv = q.vector();
Expand All @@ -203,26 +215,29 @@ io::JsonGenericValue ConfigValue::writeToJsonObject(
io::JsonAllocator& allocator) const {
// unknown is checked before this function is called, so does not need support
switch (getType()) {
case core::config::ConfigStoredType::Boolean: {
case ConfigStoredType::Boolean: {
return io::toJsonValue(get<bool>(), allocator);
}
case core::config::ConfigStoredType::Integer: {
case ConfigStoredType::Integer: {
return io::toJsonValue(get<int>(), allocator);
}
case core::config::ConfigStoredType::Double: {
case ConfigStoredType::Double: {
return io::toJsonValue(get<double>(), allocator);
}
case core::config::ConfigStoredType::MagnumVec3: {
return io::toJsonValue(get<Magnum::Vector3>(), allocator);
case ConfigStoredType::MagnumVec3: {
return io::toJsonValue(get<Mn::Vector3>(), allocator);
}
case core::config::ConfigStoredType::MagnumQuat: {
return io::toJsonValue(get<Magnum::Quaternion>(), allocator);
case ConfigStoredType::MagnumMat3: {
return io::toJsonValue(get<Mn::Matrix3>(), allocator);
}
case core::config::ConfigStoredType::MagnumRad: {
auto r = get<Magnum::Rad>();
case ConfigStoredType::MagnumQuat: {
return io::toJsonValue(get<Mn::Quaternion>(), allocator);
}
case ConfigStoredType::MagnumRad: {
auto r = get<Mn::Rad>();
return io::toJsonValue((r.operator float()), allocator);
}
case core::config::ConfigStoredType::String: {
case ConfigStoredType::String: {
return io::toJsonValue(get<std::string>(), allocator);
}
default:
Expand All @@ -247,6 +262,8 @@ bool ConfigValue::putValueInConfigGroup(
return cfg.setValue(key, get<std::string>());
case ConfigStoredType::MagnumVec3:
return cfg.setValue(key, get<Mn::Vector3>());
case ConfigStoredType::MagnumMat3:
return cfg.setValue(key, get<Mn::Matrix3>());
case ConfigStoredType::MagnumQuat:
return cfg.setValue(key, get<Mn::Quaternion>());
case ConfigStoredType::MagnumRad:
Expand Down Expand Up @@ -287,25 +304,43 @@ int Configuration::loadFromJson(const io::JsonGenericValue& jsonObj) {
set(key, obj.GetString());
} else if (obj.IsBool()) {
set(key, obj.GetBool());
} else if (obj.IsArray() && obj.Size() > 0 && obj[0].IsNumber()) {
// numeric vector or quaternion
if (obj.Size() == 3) {
Magnum::Vector3 val{};
if (io::fromJsonValue(obj, val)) {
set(key, val);
}
} else if (obj.Size() == 4) {
// assume is quaternion
Magnum::Quaternion val{};
if (io::fromJsonValue(obj, val)) {
set(key, val);
} else if (obj.IsArray() && obj.Size() > 0) {
// non-empty array of values
if (obj[0].IsNumber()) {
// numeric vector or quaternion
if (obj.Size() == 3) {
Mn::Vector3 val{};
if (io::fromJsonValue(obj, val)) {
set(key, val);
}
} else if (obj.Size() == 4) {
// assume is quaternion
Mn::Quaternion val{};
if (io::fromJsonValue(obj, val)) {
set(key, val);
}
} else if (obj.Size() == 9) {
// assume is 3x3 matrix
Mn::Matrix3 mat{};
if (io::fromJsonValue(obj, mat)) {
set(key, mat);
}
} else {
// decrement count for key:obj due to not being handled vector
--numConfigSettings;
// TODO support numeric array in JSON
ESP_WARNING()
<< "Config cell in JSON document contains key" << key
<< "referencing an unsupported numeric array of length :"
<< obj.Size() << "so skipping.";
}
} else {
// decrement count for key:obj due to not being handled vector
// decrement count for key:obj due to not being handled vector or matrix
--numConfigSettings;
// TODO support numeric array in JSON
ESP_WARNING() << "Config cell in JSON document contains key" << key
<< "referencing an unsupported numeric array of length :"
<< "referencing an unsupported array of non-numeric, "
"non-array type of length :"
<< obj.Size() << "so skipping.";
}
} else if (obj.IsObject()) {
Expand All @@ -327,7 +362,7 @@ int Configuration::loadFromJson(const io::JsonGenericValue& jsonObj) {
}
}
return numConfigSettings;
} // Configuration::loadFromJson
} // namespace config

void Configuration::writeValueToJson(const char* key,
const char* jsonName,
Expand Down
42 changes: 26 additions & 16 deletions src/esp/core/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum class ConfigStoredType {
Integer,
Double,
MagnumVec3,
MagnumMat3,
MagnumQuat,
MagnumRad,

Expand Down Expand Up @@ -92,6 +93,10 @@ constexpr ConfigStoredType configStoredTypeFor<Mn::Vector3>() {
return ConfigStoredType::MagnumVec3;
}
template <>
constexpr ConfigStoredType configStoredTypeFor<Mn::Matrix3>() {
return ConfigStoredType::MagnumMat3;
}
template <>
constexpr ConfigStoredType configStoredTypeFor<Mn::Quaternion>() {
return ConfigStoredType::MagnumQuat;
}
Expand All @@ -118,10 +123,13 @@ class ConfigValue {
ConfigStoredType _type{ConfigStoredType::Unknown};

/**
* @brief The data this ConfigValue holds - four doubles at most (strings
* require 32 bytes), doubles and 64bit pointers need 8-byte alignment
* @brief The data this ConfigValue holds.
* Aligns to individual 8-byte bounds. The pair the Configuration map holds
* consists of a std::string key (sizeof:24 bytes) and a ConfigValue. The
* _type is 4 bytes, 4 bytes of padding (on 64 bit machines) and 48 bytes for
* data.
*/
alignas(sizeof(void*) * 2) char _data[4 * 8] = {0};
alignas(8) char _data[6 * 8] = {0};

/**
* @brief Copy the passed @p val into this ConfigValue. If this @ref
Expand Down Expand Up @@ -164,25 +172,27 @@ class ConfigValue {

template <class T>
void set(const T& value) {
// this never fails
deleteCurrentValue();
// this will blow up at compile time if given type is not supported
// This will blow up at compile time if given type is not supported
_type = configStoredTypeFor<T>();
// these asserts are checking the integrity of the support for T's type, and
// will fire if...
// These asserts are checking the integrity of the support for T's type, and
// will fire if conditions are not met.

// ...we added a new type into @ref ConfigStoredType enum improperly
// (trivial type added after entry ConfigStoredType::_nonTrivialTypes, or
// vice-versa)
// This fails if we added a new type into @ref ConfigStoredType enum
// improperly (trivial type added after entry
// ConfigStoredType::_nonTrivialTypes, or vice-versa)
static_assert(isConfigStoredTypeNonTrivial(configStoredTypeFor<T>()) !=
std::is_trivially_copyable<T>::value,
"Something's incorrect about enum placement for added type!");
// ...we added a new type that is too large for internal storage
static_assert(sizeof(T) <= sizeof(_data), "internal storage too small");
// ...we added a new type whose alignment does not match internal storage
// alignment
static_assert(alignof(T) <= alignof(ConfigValue),
"internal storage too unaligned");
// This fails if a new type was added that is too large for internal storage
static_assert(
sizeof(T) <= sizeof(_data),
"ConfigValue's internal storage is too small for added type!");
// This fails if a new type was added whose alignment does not match
// internal storage alignment
static_assert(
alignof(T) <= alignof(ConfigValue),
"ConfigValue's internal storage improperly aligned for added type!");

//_data should be destructed at this point, construct a new value
new (_data) T{value};
Expand Down
21 changes: 21 additions & 0 deletions src/esp/io/JsonMagnumTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@
namespace esp {
namespace io {

JsonGenericValue toJsonValue(const Magnum::Matrix3& mat,
JsonAllocator& allocator) {
return toJsonArrayHelper(mat.data(), 9, allocator);
}

bool fromJsonValue(const JsonGenericValue& obj, Magnum::Matrix3& mat) {
if (obj.IsArray() && obj.Size() == 9) {
for (rapidjson::SizeType i = 0; i < 9; ++i) {
if (obj[i].IsNumber()) {
mat.data()[i] = obj[i].GetFloat();
} else {
ESP_ERROR()
<< "Invalid numeric value specified in JSON Matrix3, index :" << i;
return false;
}
}
return true;
}
return false;
}

JsonGenericValue toJsonValue(const Magnum::Quaternion& quat,
JsonAllocator& allocator) {
JsonGenericValue arr(rapidjson::kArrayType);
Expand Down
13 changes: 13 additions & 0 deletions src/esp/io/JsonMagnumTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@
namespace esp {
namespace io {

JsonGenericValue toJsonValue(const Magnum::Matrix3& mat,
JsonAllocator& allocator);
/**
* @brief Specialization to handle Magnum::Matrix3 values. Populate passed @p
* val with value. Returns whether successfully populated, or not. Logs an error
* if inappropriate type.
*
* @param obj json value to parse
* @param val destination value to be populated
* @return whether successful or not
*/
bool fromJsonValue(const JsonGenericValue& obj, Magnum::Matrix3& val);

inline JsonGenericValue toJsonValue(const Magnum::Vector3& vec,
JsonAllocator& allocator) {
return toJsonArrayHelper(vec.data(), 3, allocator);
Expand Down
8 changes: 8 additions & 0 deletions src/tests/CoreTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,18 @@ CoreTest::CoreTest() {
void CoreTest::TestConfiguration() {
Configuration cfg;
cfg.set("myInt", 10);
cfg.set("myFloatToDouble", 1.2f);
cfg.set("myMat3", Mn::Matrix3(Mn::Math::IdentityInit));
cfg.set("myString", "test");
CORRADE_VERIFY(cfg.hasValue("myInt"));
CORRADE_VERIFY(cfg.hasValue("myFloatToDouble"));
CORRADE_VERIFY(cfg.hasValue("myMat3"));
CORRADE_VERIFY(cfg.hasValue("myString"));
CORRADE_COMPARE(cfg.get<int>("myInt"), 10);
CORRADE_COMPARE(cfg.get<double>("myFloatToDouble"), 1.2f);
for (int i = 0; i < 3; ++i) {
CORRADE_COMPARE(cfg.get<Mn::Matrix3>("myMat3").row(i)[i], 1);
}
CORRADE_COMPARE(cfg.get<std::string>("myString"), "test");
}

Expand Down
Loading

0 comments on commit b0d9c1f

Please sign in to comment.