Skip to content

Commit

Permalink
feat: Use new FloatingPoint type (#62)
Browse files Browse the repository at this point in the history
Co-authored-by: ReenigneArcher <[email protected]>
  • Loading branch information
FrogTheFrog and ReenigneArcher authored Jul 17, 2024
1 parent 94b803f commit c0d24ce
Show file tree
Hide file tree
Showing 23 changed files with 209 additions and 81 deletions.
1 change: 1 addition & 0 deletions src/common/include/displaydevice/detail/jsonserializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace display_device {

// Structs
DD_JSON_DECLARE_SERIALIZE_TYPE(Resolution)
DD_JSON_DECLARE_SERIALIZE_TYPE(Rational)
DD_JSON_DECLARE_SERIALIZE_TYPE(Point)
DD_JSON_DECLARE_SERIALIZE_TYPE(EnumeratedDevice::Info)
DD_JSON_DECLARE_SERIALIZE_TYPE(EnumeratedDevice)
Expand Down
58 changes: 56 additions & 2 deletions src/common/include/displaydevice/detail/jsonserializerdetails.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,38 @@
}

namespace display_device {
/**
* @brief Holds information for serializing variants.
*/
namespace detail {
template <class T>
struct JsonTypeName;

template <>
struct JsonTypeName<double> {
static constexpr std::string_view m_name { "double" };
};

template <>
struct JsonTypeName<Rational> {
static constexpr std::string_view m_name { "rational" };
};

template <class T, class... Ts>
bool
variantFromJson(const nlohmann::json &nlohmann_json_j, std::variant<Ts...> &value) {
if (nlohmann_json_j.at("type").get<std::string_view>() != JsonTypeName<T>::m_name) {
return false;
}

value = nlohmann_json_j.at("value").get<T>();
return true;
}
} // namespace detail

// A shared function for enums to find values in the map. Extracted here for UTs + coverage
template <class T, class Predicate>
std::map<T, nlohmann::json>::const_iterator
typename std::map<T, nlohmann::json>::const_iterator
findInEnumMap(const char *error_msg, Predicate predicate) {
const auto &map { getEnumMap(T {}) };
auto it { std::find_if(std::begin(map), std::end(map), predicate) };
Expand All @@ -58,7 +87,7 @@ namespace display_device {

namespace nlohmann {
// Specialization for optional types until they actually implement it.
template <typename T>
template <class T>
struct adl_serializer<std::optional<T>> {
static void
to_json(json &nlohmann_json_j, const std::optional<T> &nlohmann_json_t) {
Expand All @@ -80,5 +109,30 @@ namespace nlohmann {
}
}
};

// Specialization for variant type.
// See https://github.com/nlohmann/json/issues/1261#issuecomment-2048770747
template <typename... Ts>
struct adl_serializer<std::variant<Ts...>> {
static void
to_json(json &nlohmann_json_j, const std::variant<Ts...> &nlohmann_json_t) {
std::visit(
[&nlohmann_json_j]<class T>(const T &value) {
nlohmann_json_j["type"] = display_device::detail::JsonTypeName<std::decay_t<T>>::m_name;
nlohmann_json_j["value"] = value;
},
nlohmann_json_t);
}

static void
from_json(const json &nlohmann_json_j, std::variant<Ts...> &nlohmann_json_t) {
// Call variant_from_json for all types, only one will succeed
const bool found { (display_device::detail::variantFromJson<Ts>(nlohmann_json_j, nlohmann_json_t) || ...) };
if (!found) {
const std::string error { "Could not parse variant from type " + nlohmann_json_j.at("type").get<std::string>() + "!" };
throw std::runtime_error(error);
}
}
};
} // namespace nlohmann
#endif
26 changes: 23 additions & 3 deletions src/common/include/displaydevice/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// system includes
#include <optional>
#include <string>
#include <variant>
#include <vector>

namespace display_device {
Expand Down Expand Up @@ -42,6 +43,25 @@ namespace display_device {
operator==(const Point &lhs, const Point &rhs);
};

/**
* @brief Floating point stored in a "numerator/denominator" form.
*/
struct Rational {
unsigned int m_numerator {};
unsigned int m_denominator {};

/**
* @brief Comparator for strict equality.
*/
friend bool
operator==(const Rational &lhs, const Rational &rhs);
};

/**
* @brief Floating point type.
*/
using FloatingPoint = std::variant<double, Rational>;

/**
* @brief Enumerated display device information.
*/
Expand All @@ -51,8 +71,8 @@ namespace display_device {
*/
struct Info {
Resolution m_resolution {}; /**< Resolution of an active device. */
double m_resolution_scale {}; /**< Resolution scaling of an active device. */
double m_refresh_rate {}; /**< Refresh rate of an active device. */
FloatingPoint m_resolution_scale {}; /**< Resolution scaling of an active device. */
FloatingPoint m_refresh_rate {}; /**< Refresh rate of an active device. */
bool m_primary {}; /**< Indicates whether the device is a primary display. */
Point m_origin_point {}; /**< A starting point of the display. */
std::optional<HdrState> m_hdr_state {}; /**< HDR of an active device. */
Expand Down Expand Up @@ -101,7 +121,7 @@ namespace display_device {
std::string m_device_id {}; /**< Device to perform configuration for (can be empty if primary device should be used). */
DevicePreparation m_device_prep {}; /**< Instruction on how to prepare device. */
std::optional<Resolution> m_resolution {}; /**< Resolution to configure. */
std::optional<double> m_refresh_rate {}; /**< Refresh rate to configure. */
std::optional<FloatingPoint> m_refresh_rate {}; /**< Refresh rate to configure. */
std::optional<HdrState> m_hdr_state {}; /**< HDR state to configure (if supported by the display). */

/**
Expand Down
1 change: 1 addition & 0 deletions src/common/jsonserializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace display_device {

// Structs
DD_JSON_DEFINE_SERIALIZE_STRUCT(Resolution, width, height)
DD_JSON_DEFINE_SERIALIZE_STRUCT(Rational, numerator, denominator)
DD_JSON_DEFINE_SERIALIZE_STRUCT(Point, x, y)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice::Info, resolution, resolution_scale, refresh_rate, primary, origin_point, hdr_state)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice, device_id, display_name, friendly_name, info)
Expand Down
18 changes: 17 additions & 1 deletion src/common/types.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
// local includes
// header include
#include "displaydevice/types.h"

namespace {
bool
fuzzyCompare(const double lhs, const double rhs) {
return std::abs(lhs - rhs) * 1000000000000. <= std::min(std::abs(lhs), std::abs(rhs));
}

bool
fuzzyCompare(const display_device::FloatingPoint &lhs, const display_device::FloatingPoint &rhs) {
if (lhs.index() == rhs.index()) {
if (std::holds_alternative<double>(lhs)) {
return fuzzyCompare(std::get<double>(lhs), std::get<double>(rhs));
}
return lhs == rhs;
}
return false;
}
} // namespace

namespace display_device {
bool
operator==(const Rational &lhs, const Rational &rhs) {
return lhs.m_numerator == rhs.m_numerator && lhs.m_denominator == rhs.m_denominator;
}

bool
operator==(const Point &lhs, const Point &rhs) {
return lhs.m_x == rhs.m_x && lhs.m_y == rhs.m_y;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#ifdef DD_JSON_DETAIL
namespace display_device {
// Structs
DD_JSON_DECLARE_SERIALIZE_TYPE(Rational)
DD_JSON_DECLARE_SERIALIZE_TYPE(DisplayMode)
DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState::Initial)
DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState::Modified)
Expand Down
2 changes: 1 addition & 1 deletion src/windows/include/displaydevice/windows/settingsutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ namespace display_device::win_utils {
*/
DeviceDisplayModeMap
computeNewDisplayModes(const std::optional<Resolution> &resolution,
const std::optional<double> &refresh_rate,
const std::optional<FloatingPoint> &refresh_rate,
bool configuring_primary_devices,
const std::string &device_to_configure,
const std::set<std::string> &additional_devices_to_configure,
Expand Down
20 changes: 0 additions & 20 deletions src/windows/include/displaydevice/windows/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,6 @@ namespace display_device {
*/
using ActiveTopology = std::vector<std::vector<std::string>>;

/**
* @brief Floating point stored in a "numerator/denominator" form.
*/
struct Rational {
unsigned int m_numerator {};
unsigned int m_denominator {};

/**
* @brief Contruct rational struct from double precission floating point.
*/
static Rational
fromFloatingPoint(double value);

/**
* @brief Comparator for strict equality.
*/
friend bool
operator==(const Rational &lhs, const Rational &rhs);
};

/**
* @brief Display's mode (resolution + refresh rate).
*/
Expand Down
2 changes: 1 addition & 1 deletion src/windows/include/displaydevice/windows/winapilayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace display_device {
setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) override;

/** For details @see WinApiLayerInterface::getDisplayScale */
[[nodiscard]] std::optional<double>
[[nodiscard]] std::optional<Rational>
getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const override;
};
} // namespace display_device
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ namespace display_device {
* const auto scale = iface->getDisplayScale(iface->getDisplayName(path), source_mode);
* ```
*/
[[nodiscard]] virtual std::optional<double>
[[nodiscard]] virtual std::optional<Rational>
getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const = 0;
};
} // namespace display_device
1 change: 0 additions & 1 deletion src/windows/jsonserializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

namespace display_device {
// Structs
DD_JSON_DEFINE_SERIALIZE_STRUCT(Rational, numerator, denominator)
DD_JSON_DEFINE_SERIALIZE_STRUCT(DisplayMode, resolution, refresh_rate)
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState::Initial, topology, primary_devices)
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState::Modified, topology, original_modes, original_hdr_states, original_primary_device)
Expand Down
20 changes: 17 additions & 3 deletions src/windows/settingsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

// system includes
#include <algorithm>
#include <cmath>
#include <thread>

// local includes
Expand Down Expand Up @@ -264,7 +265,7 @@ namespace display_device::win_utils {
}

DeviceDisplayModeMap
computeNewDisplayModes(const std::optional<Resolution> &resolution, const std::optional<double> &refresh_rate, const bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes) {
computeNewDisplayModes(const std::optional<Resolution> &resolution, const std::optional<FloatingPoint> &refresh_rate, const bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes) {
DeviceDisplayModeMap new_modes { original_modes };

if (resolution) {
Expand All @@ -278,19 +279,32 @@ namespace display_device::win_utils {
}

if (refresh_rate) {
const auto from_floating_point { [](const FloatingPoint &value) {
if (const auto *rational_value { std::get_if<Rational>(&value) }; rational_value) {
return *rational_value;
}

// It's hard to deal with floating values, so we just multiply it
// to keep 4 decimal places (if any) and let Windows deal with it!
// Genius idea if I'm being honest.
constexpr auto multiplier { static_cast<unsigned int>(std::pow(10, 4)) };
const double transformed_value { std::round(std::get<double>(value) * multiplier) };
return Rational { static_cast<unsigned int>(transformed_value), multiplier };
} };

if (configuring_primary_devices) {
// No device has been specified, so if they're all are primary devices
// we need to apply the refresh rate change to all duplicates.
const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) };
for (const auto &device_id : devices) {
new_modes[device_id].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate);
new_modes[device_id].m_refresh_rate = from_floating_point(*refresh_rate);
}
}
else {
// Even if we have duplicate devices, their refresh rate may differ
// and since the device was specified, let's apply the refresh
// rate only to the specified device.
new_modes[device_to_configure].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate);
new_modes[device_to_configure].m_refresh_rate = from_floating_point(*refresh_rate);
}
}

Expand Down
18 changes: 0 additions & 18 deletions src/windows/types.cpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,7 @@
// header include
#include "displaydevice/windows/types.h"

// system includes
#include <cmath>

namespace display_device {
Rational
Rational::fromFloatingPoint(const double value) {
// It's hard to deal with floating values, so we just multiply it
// to keep 4 decimal places (if any) and let Windows deal with it!
// Genius idea if I'm being honest.
constexpr auto multiplier { static_cast<unsigned int>(std::pow(10, 4)) };
const double transformed_value { std::round(value * multiplier) };
return Rational { static_cast<unsigned int>(transformed_value), multiplier };
}

bool
operator==(const Rational &lhs, const Rational &rhs) {
return lhs.m_numerator == rhs.m_numerator && lhs.m_denominator == rhs.m_denominator;
}

bool
operator==(const DisplayMode &lhs, const DisplayMode &rhs) {
return lhs.m_refresh_rate == rhs.m_refresh_rate && lhs.m_resolution == rhs.m_resolution;
Expand Down
5 changes: 3 additions & 2 deletions src/windows/winapilayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <boost/uuid/name_generator_sha1.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <cmath>
#include <cstdint>
#include <iomanip>

Expand Down Expand Up @@ -575,7 +576,7 @@ namespace display_device {
return true;
}

std::optional<double>
std::optional<Rational>
WinApiLayer::getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const {
// Note: implementation based on https://stackoverflow.com/a/74046173
struct EnumData {
Expand Down Expand Up @@ -617,6 +618,6 @@ namespace display_device {
}

const auto width { static_cast<double>(*enum_data.m_width) / static_cast<double>(source_mode.width) };
return static_cast<double>(GetDpiForSystem()) / 96. / width;
return Rational { static_cast<unsigned int>(std::round((static_cast<double>(GetDpiForSystem()) / 96. / width) * 100)), 100 };
}
} // namespace display_device
10 changes: 5 additions & 5 deletions src/windows/windisplaydevicegeneral.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ namespace display_device {
}

const auto display_name { m_w_api->getDisplayName(best_path) };
const double refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ?
static_cast<double>(best_path.targetInfo.refreshRate.Numerator) / static_cast<double>(best_path.targetInfo.refreshRate.Denominator) :
0. };
const Rational refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ?
Rational { best_path.targetInfo.refreshRate.Numerator, best_path.targetInfo.refreshRate.Denominator } :
Rational { 0, 1 } };
const EnumeratedDevice::Info info {
{ source_mode->width, source_mode->height },
m_w_api->getDisplayScale(display_name, *source_mode).value_or(0.),
m_w_api->getDisplayScale(display_name, *source_mode).value_or(Rational { 0, 1 }),
refresh_rate,
win_utils::isPrimary(*source_mode),
{ source_mode->position.x, source_mode->position.y },
{ static_cast<int>(source_mode->position.x), static_cast<int>(source_mode->position.y) },
m_w_api->getHdrState(best_path)
};

Expand Down
5 changes: 4 additions & 1 deletion tests/fixtures/include/fixtures/jsonconvertertest.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ class JsonConverterTest: public BaseTest {
EXPECT_TRUE(success);
EXPECT_EQ(json_string, expected_string);

std::string error_message {};
T defaulted_input {};
EXPECT_TRUE(display_device::fromJson(json_string, defaulted_input));
if (!display_device::fromJson(json_string, defaulted_input, &error_message)) {
GTEST_FAIL() << error_message;
}
EXPECT_EQ(input, defaulted_input);
}
};
Loading

0 comments on commit c0d24ce

Please sign in to comment.