From 30f9d3b459ce05b739eba5ac9a87dc20c8b676a3 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Sun, 2 Jun 2024 10:34:42 -0300 Subject: [PATCH] Implemented numeric_item template class, switched to type-safer code for passing seconds around. --- include/cfg.hpp | 7 +- include/core.hpp | 5 +- include/ntp.hpp | 14 ++- include/time_utils.hpp | 48 +++++++++ include/utc.hpp | 7 +- include/utils.hpp | 8 -- include/wupsxx/duration_item.hpp | 61 ----------- include/wupsxx/duration_items.hpp | 19 ++++ include/wupsxx/int_item.hpp | 47 +-------- include/wupsxx/numeric_item.hpp | 63 +++++++++++ include/wupsxx/storage.hpp | 20 ++++ source/cfg.cpp | 62 ++++------- source/clock_item.cpp | 37 ++++--- source/config_screen.cpp | 32 +++--- source/core.cpp | 73 +++++++------ source/notify.cpp | 6 +- source/ntp.cpp | 12 ++- source/time_utils.cpp | 78 ++++++++++++++ source/timezone_offset_item.cpp | 6 +- source/timezone_query_item.cpp | 2 +- source/utc.cpp | 8 +- source/utils.cpp | 39 ------- source/verbosity_item.cpp | 9 +- source/wupsxx/duration_item.cpp | 133 ------------------------ source/wupsxx/duration_items.cpp | 11 ++ source/wupsxx/int_item.cpp | 131 +---------------------- source/wupsxx/numeric_item_impl.hpp | 155 ++++++++++++++++++++++++++++ 27 files changed, 540 insertions(+), 553 deletions(-) create mode 100644 include/time_utils.hpp delete mode 100644 include/wupsxx/duration_item.hpp create mode 100644 include/wupsxx/duration_items.hpp create mode 100644 include/wupsxx/numeric_item.hpp create mode 100644 source/time_utils.cpp delete mode 100644 source/wupsxx/duration_item.cpp create mode 100644 source/wupsxx/duration_items.cpp create mode 100644 source/wupsxx/numeric_item_impl.hpp diff --git a/include/cfg.hpp b/include/cfg.hpp index db038bb..99f331b 100644 --- a/include/cfg.hpp +++ b/include/cfg.hpp @@ -34,7 +34,7 @@ namespace cfg { namespace defaults { extern const bool auto_tz; - extern const int msg_duration; + extern const std::chrono::seconds msg_duration; extern const int notify; extern const std::string server; extern const bool sync; @@ -44,7 +44,7 @@ namespace cfg { extern bool auto_tz; - extern int msg_duration; + extern std::chrono::seconds msg_duration; extern int notify; extern std::string server; extern bool sync; @@ -58,8 +58,7 @@ namespace cfg { void save(); void migrate_old_config(); - std::chrono::minutes get_utc_offset(); - void set_utc_offset(std::chrono::minutes tz_offset); + void set_and_store_utc_offset(std::chrono::minutes tz_offset); } // namespace cfg diff --git a/include/core.hpp b/include/core.hpp index 7a3cb89..83768ae 100644 --- a/include/core.hpp +++ b/include/core.hpp @@ -7,11 +7,14 @@ #include // pair<> #include "net/address.hpp" +#include "time_utils.hpp" namespace core { - std::pair ntp_query(net::address address); + using time_utils::dbl_seconds; + + std::pair ntp_query(net::address address); void run(); diff --git a/include/ntp.hpp b/include/ntp.hpp index 1cbd01f..a857ec2 100644 --- a/include/ntp.hpp +++ b/include/ntp.hpp @@ -7,9 +7,15 @@ #include #include +#include "time_utils.hpp" + + +// For details, see https://www.ntp.org/reflib/rfc/rfc5905.txt namespace ntp { - // For details, see https://www.ntp.org/reflib/rfc/rfc5905.txt + + using time_utils::dbl_seconds; + // This is u32.32 fixed-point format, seconds since 1900-01-01 00:00:00 UTC class timestamp { @@ -22,9 +28,9 @@ namespace ntp { timestamp(std::uint64_t v) = delete; - // Allow explicit conversions from/to double - explicit timestamp(double d) noexcept; - explicit operator double() const noexcept; + // Allow explicit conversions from/to dbl_seconds + explicit timestamp(dbl_seconds d) noexcept; + explicit operator dbl_seconds() const noexcept; // Checks if timestamp is non-zero. Zero has a special meaning. constexpr explicit operator bool() const noexcept { return stored; } diff --git a/include/time_utils.hpp b/include/time_utils.hpp new file mode 100644 index 0000000..7769411 --- /dev/null +++ b/include/time_utils.hpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +#ifndef TIME_UTILS_HPP +#define TIME_UTILS_HPP + +#include +#include +#include + + +namespace time_utils { + + // Type-safe way to pass seconds around as double + using dbl_seconds = std::chrono::duration; + + + // Type trait to identify when a type is std::chrono::duration<> + + template + struct is_duration : std::false_type {}; + + template + struct is_duration> : std::true_type {}; + + // convenience variable template + template + constexpr bool is_duration_v = is_duration::value; + + + template + concept duration = is_duration_v; + + + template + std::string to_string(T t); + + + + // Generate time duration strings for humans. + std::string seconds_to_human(dbl_seconds s, bool show_positive = false); + + + std::string tz_offset_to_string(std::chrono::minutes offset); + +} + + +#endif diff --git a/include/utc.hpp b/include/utc.hpp index 6c7f041..948f6cf 100644 --- a/include/utc.hpp +++ b/include/utc.hpp @@ -3,12 +3,17 @@ #ifndef UTC_HPP #define UTC_HPP +#include "time_utils.hpp" + namespace utc { + using time_utils::dbl_seconds; + + // Seconds since 2000-01-01 00:00:00 UTC struct timestamp { - double value; + dbl_seconds value; }; diff --git a/include/utils.hpp b/include/utils.hpp index 662b91b..6393d3e 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -13,10 +13,6 @@ namespace utils { - // Generate time duration strings for humans. - std::string seconds_to_human(double s, bool show_positive = false); - - /** * Split input string into tokens, according to separators. * @@ -46,10 +42,6 @@ namespace utils { std::chrono::minutes> fetch_timezone(); - - std::string tz_offset_to_string(std::chrono::minutes offset); - - } // namespace utils #endif diff --git a/include/wupsxx/duration_item.hpp b/include/wupsxx/duration_item.hpp deleted file mode 100644 index f8922ac..0000000 --- a/include/wupsxx/duration_item.hpp +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT - -#ifndef WUPSXX_DURATION_ITEM_HPP -#define WUPSXX_DURATION_ITEM_HPP - -#include -#include - -#include "item.hpp" -#include "var_watch.hpp" - - -namespace wups::config { - - class duration_item : public item { - - protected: - - var_watch variable; - const std::chrono::milliseconds default_value; - std::chrono::milliseconds min_value; - std::chrono::milliseconds max_value; - std::chrono::milliseconds fast_increment; - std::chrono::milliseconds slow_increment; - - public: - - duration_item(const std::optional& key, - const std::string& label, - std::chrono::milliseconds& variable, std::chrono::milliseconds default_value, - std::chrono::milliseconds min_value, std::chrono::milliseconds max_value, - std::chrono::milliseconds fast_increment = std::chrono::milliseconds(1000), - std::chrono::milliseconds slow_increment = std::chrono::milliseconds(1)); - - static - std::unique_ptr - create(const std::optional& key, - const std::string& label, - std::chrono::milliseconds& variable, std::chrono::milliseconds default_value, - std::chrono::milliseconds min_value, std::chrono::milliseconds max_value, - std::chrono::milliseconds fast_increment = std::chrono::milliseconds(1000), - std::chrono::milliseconds slow_increment = std::chrono::milliseconds(1)); - - virtual int get_display(char* buf, std::size_t size) const override; - - virtual int get_selected_display(char* buf, std::size_t size) const override; - - virtual void restore() override; - - virtual void on_input(WUPSConfigSimplePadData input, - WUPS_CONFIG_SIMPLE_INPUT repeat) override; - - private: - - void on_changed(); - - }; - -} // namespace wups::config - -#endif \ No newline at end of file diff --git a/include/wupsxx/duration_items.hpp b/include/wupsxx/duration_items.hpp new file mode 100644 index 0000000..131599e --- /dev/null +++ b/include/wupsxx/duration_items.hpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +#ifndef WUPSXX_DURATION_ITEMS_HPP +#define WUPSXX_DURATION_ITEMS_HPP + +#include + +#include "numeric_item.hpp" + + +namespace wups::config { + + using milliseconds_item = numeric_item; + + using seconds_item = numeric_item; + +} // namespace wups::config + +#endif diff --git a/include/wupsxx/int_item.hpp b/include/wupsxx/int_item.hpp index ec77e7a..148f1a1 100644 --- a/include/wupsxx/int_item.hpp +++ b/include/wupsxx/int_item.hpp @@ -5,55 +5,12 @@ #include -#include "item.hpp" -#include "var_watch.hpp" +#include "numeric_item.hpp" namespace wups::config { - class int_item : public item { - - protected: - - var_watch variable; - const int default_value; - int min_value; - int max_value; - int fast_increment; - int slow_increment; - - public: - - int_item(const std::optional& key, - const std::string& label, - int& variable, int default_value, - int min_value, int max_value, - int fast_increment = 10, - int slow_increment = 1); - - static - std::unique_ptr - create(const std::optional& key, - const std::string& label, - int& variable, int default_value, - int min_value, int max_value, - int fast_increment = 10, - int slow_increment = 1); - - virtual int get_display(char* buf, std::size_t size) const override; - - virtual int get_selected_display(char* buf, std::size_t size) const override; - - virtual void restore() override; - - virtual void on_input(WUPSConfigSimplePadData input, - WUPS_CONFIG_SIMPLE_INPUT repeat) override; - - private: - - void on_changed(); - - }; + using int_item = numeric_item; } // namespace wups::config diff --git a/include/wupsxx/numeric_item.hpp b/include/wupsxx/numeric_item.hpp new file mode 100644 index 0000000..65b88cc --- /dev/null +++ b/include/wupsxx/numeric_item.hpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT + +#ifndef WUPSXX_NUMERIC_ITEM_HPP +#define WUPSXX_NUMERIC_ITEM_HPP + +#include + +#include "item.hpp" +#include "var_watch.hpp" + + +namespace wups::config { + + + template + class numeric_item : public item { + + protected: + + var_watch variable; + const T default_value; + T min_value; + T max_value; + T fast_increment; + T slow_increment; + + public: + + numeric_item(const std::optional& key, + const std::string& label, + T& variable, T default_value, + T min_value, T max_value, + T fast_increment = T{10}, + T slow_increment = T{1}); + + static + std::unique_ptr + create(const std::optional& key, + const std::string& label, + T& variable, T default_value, + T min_value, T max_value, + T fast_increment = T{10}, + T slow_increment = T{1}); + + + virtual int get_display(char* buf, std::size_t size) const override; + + virtual int get_selected_display(char* buf, std::size_t size) const override; + + virtual void restore() override; + + virtual void on_input(WUPSConfigSimplePadData input, + WUPS_CONFIG_SIMPLE_INPUT repeat) override; + + private: + + void on_changed(); + + }; + +} // namespace wups::config + +#endif diff --git a/include/wupsxx/storage.hpp b/include/wupsxx/storage.hpp index a59fea1..2a40087 100644 --- a/include/wupsxx/storage.hpp +++ b/include/wupsxx/storage.hpp @@ -9,6 +9,7 @@ #include #include "storage_error.hpp" +#include "../time_utils.hpp" namespace wups::storage { @@ -29,6 +30,17 @@ namespace wups::storage { } + template + std::expected + load(const std::string& key) + { + auto value = load(key); + if (!value) + return std::unexpected{value.error()}; + return T{*value}; + } + + template void store(const std::string& key, const T& value) @@ -40,6 +52,14 @@ namespace wups::storage { } + template + void + store(const std::string& key, const T& value) + { + return store(key, value.count()); + } + + void save(); void reload(); diff --git a/source/cfg.cpp b/source/cfg.cpp index 7057c5a..004ebcc 100644 --- a/source/cfg.cpp +++ b/source/cfg.cpp @@ -3,12 +3,15 @@ #include "cfg.hpp" #include "logging.hpp" +#include "time_utils.hpp" #include "utils.hpp" #include "wupsxx/storage.hpp" -using std::chrono::minutes; +using std::chrono::hours; using std::chrono::milliseconds; +using std::chrono::minutes; +using std::chrono::seconds; using namespace std::literals; @@ -41,7 +44,7 @@ namespace cfg { namespace defaults { const bool auto_tz = false; - const int msg_duration = 5; + const seconds msg_duration = 5s; const int notify = 0; const std::string server = "pool.ntp.org"; const bool sync = false; @@ -51,7 +54,7 @@ namespace cfg { bool auto_tz = defaults::auto_tz; - int msg_duration = defaults::msg_duration; + seconds msg_duration = defaults::msg_duration; int notify = defaults::notify; std::string server = defaults::server; bool sync = defaults::sync; @@ -73,30 +76,6 @@ namespace cfg { } - void - load_or_init(const std::string& key, - minutes& variable) - { - auto val = wups::storage::load(key); - if (!val) - wups::storage::store(key, variable.count()); - else - variable = minutes{*val}; - } - - - void - load_or_init(const std::string& key, - milliseconds& variable) - { - auto val = wups::storage::load(key); - if (!val) - wups::storage::store(key, variable.count()); - else - variable = milliseconds{*val}; - } - - void load() { @@ -147,31 +126,26 @@ namespace cfg { migrate_old_config() { // check for leftovers from old versions - auto hrs = wups::storage::load("hours"); - auto min = wups::storage::load("minutes"); + auto hrs = wups::storage::load("hours"); + auto min = wups::storage::load("minutes"); if (hrs || min) { - int h = hrs.value_or(0); - int m = min.value_or(0); - set_utc_offset(minutes{h * 60 + m}); + hours h = hrs.value_or(0h); + minutes m = min.value_or(0min); + minutes offset = h + m; + set_and_store_utc_offset(offset); WUPSStorageAPI::DeleteItem("hours"); WUPSStorageAPI::DeleteItem("minutes"); save(); - logging::printf("Migrated old config: %d hrs + %d min -> %s.", - h, m, - utils::tz_offset_to_string(get_utc_offset()).c_str()); + logging::printf("Migrated old config: %s + %s -> %s.", + time_utils::to_string(h).c_str(), + time_utils::to_string(m).c_str(), + time_utils::tz_offset_to_string(utc_offset).c_str()); } } - std::chrono::minutes - get_utc_offset() - { - return utc_offset; - } - - void - set_utc_offset(std::chrono::minutes offset) + set_and_store_utc_offset(minutes offset) { /* * Normally, `utc_offset` is saved on the config storage by the @@ -180,7 +154,7 @@ namespace cfg { */ utc_offset = offset; try { - wups::storage::store(key::utc_offset, utc_offset.count()); + wups::storage::store(key::utc_offset, utc_offset); wups::storage::save(); } catch (std::exception& e) { diff --git a/source/clock_item.cpp b/source/clock_item.cpp index bdec8be..575cc06 100644 --- a/source/clock_item.cpp +++ b/source/clock_item.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -#include // fmax(), fmin() +#include // max(), min() #include #include @@ -11,36 +11,39 @@ #include "logging.hpp" #include "net/addrinfo.hpp" #include "nintendo_glyphs.h" +#include "time_utils.hpp" #include "utils.hpp" +using namespace std::literals; +using time_utils::dbl_seconds; using wups::config::text_item; -using namespace std::literals; - namespace { + template struct statistics { - double min = 0; - double max = 0; - double avg = 0; + T min = T{0}; + T max = T{0}; + T avg = T{0}; }; - statistics - get_statistics(const std::vector& values) + template + statistics + get_statistics(const std::vector& values) { - statistics result; - double total = 0; + statistics result; + T total = T{0}; if (values.empty()) return result; result.min = result.max = values.front(); for (auto x : values) { - result.min = std::fmin(result.min, x); - result.max = std::fmax(result.max, x); + result.min = std::min(result.min, x); + result.max = std::max(result.max, x); total += x; } @@ -95,7 +98,7 @@ void clock_item::run() { using std::to_string; - using utils::seconds_to_human; + using time_utils::seconds_to_human; for (auto& [key, value] : server_infos) { value.name->text.clear(); @@ -107,7 +110,7 @@ clock_item::run() net::addrinfo::hints opts{ .type = net::socket::type::udp }; - double total = 0; + dbl_seconds total = 0s; unsigned num_values = 0; for (const auto& server : servers) { @@ -118,8 +121,8 @@ clock_item::run() si.name->text = to_string(infos.size()) + (infos.size() > 1 ? " addresses."s : " address."s); - std::vector server_corrections; - std::vector server_latencies; + std::vector server_corrections; + std::vector server_latencies; unsigned errors = 0; for (const auto& info : infos) { @@ -164,7 +167,7 @@ clock_item::run() } if (num_values) { - double avg = total / num_values; + dbl_seconds avg = total / num_values; stats_str = ", needs "s + seconds_to_human(avg, true); } else stats_str = ""; diff --git a/source/config_screen.cpp b/source/config_screen.cpp index 5a24ef7..06e1eb1 100644 --- a/source/config_screen.cpp +++ b/source/config_screen.cpp @@ -3,20 +3,24 @@ #include #include // make_unique() -#include "wupsxx/bool_item.hpp" -#include "wupsxx/int_item.hpp" -#include "wupsxx/text_item.hpp" - #include "config_screen.hpp" #include "cfg.hpp" #include "timezone_offset_item.hpp" #include "timezone_query_item.hpp" #include "verbosity_item.hpp" +#include "wupsxx/bool_item.hpp" +#include "wupsxx/duration_items.hpp" +#include "wupsxx/int_item.hpp" +#include "wupsxx/text_item.hpp" +#include "wupsxx/numeric_item.hpp" using wups::config::bool_item; using wups::config::int_item; +using wups::config::milliseconds_item; +using wups::config::numeric_item; +using wups::config::seconds_item; using wups::config::text_item; using namespace std::literals; @@ -38,11 +42,11 @@ make_config_screen() cfg::notify, cfg::defaults::notify)); - cat.add(int_item::create(cfg::key::msg_duration, - cfg::label::msg_duration, - cfg::msg_duration, - cfg::defaults::msg_duration, - 1, 30, 5)); + cat.add(seconds_item::create(cfg::key::msg_duration, + cfg::label::msg_duration, + cfg::msg_duration, + cfg::defaults::msg_duration, + 1s, 30s, 5s)); cat.add(timezone_query_item::create()); @@ -56,11 +60,11 @@ make_config_screen() cfg::label::utc_offset, cfg::utc_offset)); - cat.add(int_item::create(cfg::key::tolerance, - cfg::label::tolerance, - cfg::tolerance, - cfg::defaults::tolerance, - 0, 5000, 100)); + cat.add(milliseconds_item::create(cfg::key::tolerance, + cfg::label::tolerance, + cfg::tolerance, + cfg::defaults::tolerance, + 0ms, 5000ms, 100ms)); cat.add(int_item::create(cfg::key::threads, cfg::label::threads, diff --git a/source/core.cpp b/source/core.cpp index f34a1f1..fe195f6 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -2,7 +2,6 @@ #include #include -#include // fabs() #include // snprintf() #include // accumulate() #include // views::zip() @@ -25,19 +24,22 @@ #include "notify.hpp" #include "ntp.hpp" #include "thread_pool.hpp" +#include "time_utils.hpp" #include "utc.hpp" #include "utils.hpp" using namespace std::literals; +using time_utils::dbl_seconds; + namespace { // Difference from NTP (1900) to Wii U (2000) epochs. // There are 24 leap years in this period. - constexpr double seconds_per_day = 24 * 60 * 60; - constexpr double epoch_diff = seconds_per_day * (100 * 365 + 24); + constexpr dbl_seconds seconds_per_day{24 * 60 * 60}; + constexpr dbl_seconds epoch_diff = seconds_per_day * (100 * 365 + 24); // Wii U -> NTP epoch. @@ -52,7 +54,7 @@ namespace { utc::timestamp to_utc(ntp::timestamp t) { - return utc::timestamp{static_cast(t) - epoch_diff}; + return utc::timestamp{static_cast(t) - epoch_diff}; } @@ -74,7 +76,7 @@ namespace { to_string(ntp::timestamp t) { auto ut = to_utc(t); - OSTime ticks = ut.value * OSTimerClockSpeed; + OSTime ticks = ut.value.count() * OSTimerClockSpeed; return ticks_to_string(ticks); } @@ -84,7 +86,7 @@ namespace { namespace core { // Note: hardcoded for IPv4, the Wii U doesn't have IPv6. - std::pair + std::pair ntp_query(net::address address) { using std::to_string; @@ -177,36 +179,38 @@ namespace core { * fractional bits in Era 0, and 20 fractional bits in Era 1 (starting in 2036). We * still have sub-microsecond resolution. */ - double d1 = static_cast(t1); - double d2 = static_cast(t2); - double d3 = static_cast(t3); - double d4 = static_cast(t4); + auto d1 = static_cast(t1); + auto d2 = static_cast(t2); + auto d3 = static_cast(t3); + auto d4 = static_cast(t4); // Detect the wraparound that will happen at the end of Era 0. + constexpr dbl_seconds half_era{0x1.0p32}; // 2^32 seconds + constexpr dbl_seconds quarter_era{0x1.0p31}; // 2^31 seconds if (d4 < d1) - d4 += 0x1.0p32; // d4 += 2^32 + d4 += half_era; // d4 += 2^32 if (d3 < d2) - d3 += 0x1.0p32; // d3 += 2^32 + d3 += half_era; // d3 += 2^32 - double roundtrip = (d4 - d1) - (d3 - d2); - double latency = roundtrip / 2; + dbl_seconds roundtrip = (d4 - d1) - (d3 - d2); + dbl_seconds latency = roundtrip / 2.0; // t4 + correction = t3 + latency - double correction = d3 + latency - d4; + dbl_seconds correction = d3 + latency - d4; /* * If the local clock enters Era 1 ahead of NTP, we get a massive positive correction * because the local clock wrapped back to zero. */ - if (correction > 0x1.0p31) // if correcting more than 68 years forward - correction -= 0x1.0p32; + if (correction > quarter_era) // if correcting more than 68 years forward + correction -= half_era; /* * If NTP enters Era 1 ahead of the local clock, we get a massive negative correction * because NTP wrapped back to zero. */ - if (correction < -0x1.0p31) // if correcting more than 68 years backward - correction += 0x1.0p32; + if (correction < -quarter_era) // if correcting more than 68 years backward + correction += half_era; return { correction, latency }; } @@ -214,11 +218,11 @@ namespace core { bool - apply_clock_correction(double seconds) + apply_clock_correction(dbl_seconds seconds) { // OSTime before = OSGetSystemTime(); - OSTime ticks = seconds * OSTimerClockSpeed; + OSTime ticks = seconds.count() * OSTimerClockSpeed; nn::pdm::NotifySetTimeBeginEvent(); @@ -248,7 +252,7 @@ namespace core { void run() { - using utils::seconds_to_human; + using time_utils::seconds_to_human; if (!cfg::sync) return; @@ -269,11 +273,11 @@ namespace core { if (cfg::auto_tz) { try { auto [name, offset] = utils::fetch_timezone(); - if (offset != cfg::get_utc_offset()) { - cfg::set_utc_offset(offset); + if (offset != cfg::utc_offset) { + cfg::set_and_store_utc_offset(offset); notify::info(notify::level::verbose, "Auto-updated timezone to " + name + - "(" + utils::tz_offset_to_string(offset) + ")"); + "(" + time_utils::tz_offset_to_string(offset) + ")"); } } catch (std::exception& e) { @@ -318,12 +322,13 @@ namespace core { } // Launch NTP queries asynchronously. - std::vector>> futures(addresses.size()); + std::vector>> + futures(addresses.size()); for (auto [fut, address] : std::views::zip(futures, addresses)) fut = pool.submit(ntp_query, address); // Collect all future results. - std::vector corrections; + std::vector corrections; for (auto [address, fut] : std::views::zip(addresses, futures)) try { auto [correction, latency] = fut.get(); @@ -345,27 +350,27 @@ namespace core { return; } - double avg_correction = std::accumulate(corrections.begin(), + dbl_seconds total_cor = std::accumulate(corrections.begin(), corrections.end(), - 0.0) - / corrections.size(); + dbl_seconds{0}); + dbl_seconds avg = total_cor / static_cast(corrections.size()); - if (std::fabs(avg_correction) * 1000 <= cfg::tolerance) { + if (abs(avg) <= cfg::tolerance) { notify::success(notify::level::verbose, "Tolerating clock drift (correction is only " - + seconds_to_human(avg_correction, true) + ")."s); + + seconds_to_human(avg, true) + ")."s); return; } if (cfg::sync) { - if (!apply_clock_correction(avg_correction)) { + if (!apply_clock_correction(avg)) { // This error woudl be so bad, the user should always know about it. notify::error(notify::level::quiet, "Failed to set system clock!"); return; } notify::success(notify::level::normal, - "Clock corrected by " + seconds_to_human(avg_correction, true)); + "Clock corrected by " + seconds_to_human(avg, true)); } } diff --git a/source/notify.cpp b/source/notify.cpp index 31b102d..f4aaf9b 100644 --- a/source/notify.cpp +++ b/source/notify.cpp @@ -50,7 +50,7 @@ namespace notify { std::string msg = "[" PLUGIN_NAME "] " + arg; NotificationModule_AddErrorNotificationEx(msg.c_str(), - cfg::msg_duration, + cfg::msg_duration.count(), 1, {255, 255, 255, 255}, {160, 32, 32, 255}, @@ -70,7 +70,7 @@ namespace notify { std::string msg = "[" PLUGIN_NAME "] " + arg; NotificationModule_AddInfoNotificationEx(msg.c_str(), - cfg::msg_duration, + cfg::msg_duration.count(), {255, 255, 255, 255}, {32, 32, 160, 255}, nullptr, @@ -89,7 +89,7 @@ namespace notify { std::string msg = "[" PLUGIN_NAME "] " + arg; NotificationModule_AddInfoNotificationEx(msg.c_str(), - cfg::msg_duration, + cfg::msg_duration.count(), {255, 255, 255, 255}, {32, 160, 32, 255}, nullptr, diff --git a/source/ntp.cpp b/source/ntp.cpp index 8997d00..3991c81 100644 --- a/source/ntp.cpp +++ b/source/ntp.cpp @@ -10,17 +10,21 @@ namespace ntp { - timestamp::timestamp(double d) + timestamp::timestamp(dbl_seconds s) noexcept { - store(std::ldexp(d, 32)); + // shift s to the left by 32 bits, to line up the fixed point + std::uint64_t shifted_s = std::ldexp(s.count(), 32); + store(shifted_s); } - timestamp::operator double() + timestamp::operator dbl_seconds() const noexcept { - return std::ldexp(static_cast(load()), -32); + // shift to the right by 32 bits + double s = std::ldexp(static_cast(load()), -32); + return dbl_seconds{s}; } diff --git a/source/time_utils.cpp b/source/time_utils.cpp new file mode 100644 index 0000000..5f4f169 --- /dev/null +++ b/source/time_utils.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +#include // abs() +#include // snprintf() + +#include "time_utils.hpp" + + +using std::chrono::milliseconds; +using std::chrono::seconds; +using std::chrono::minutes; +using std::chrono::hours; + +using namespace std::literals; + + +namespace time_utils { + + // to_string() functions that show the time unit + + template<> + std::string + to_string(milliseconds t) + { return std::to_string(t.count()) + " ms"; } + + template<> + std::string + to_string(seconds t) + { return std::to_string(t.count()) + " s"; } + + template<> + std::string + to_string(minutes t) + { return std::to_string(t.count()) + " min"; } + + template<> + std::string + to_string(hours t) + { return std::to_string(t.count()) + " h"; } + + + std::string + seconds_to_human(dbl_seconds s, bool show_positive) + { + char buf[64]; + + if (abs(s) < 2s) + std::snprintf(buf, sizeof buf, "%.1f ms", 1000 * s.count()); + else if (abs(s) < 2min) + std::snprintf(buf, sizeof buf, "%.1f s", s.count()); + else if (abs(s) < 2h) + std::snprintf(buf, sizeof buf, "%.1f min", s.count() / 60); + else if (abs(s) < 48h) + std::snprintf(buf, sizeof buf, "%.1f hrs", s.count() / (60 * 60)); + else + std::snprintf(buf, sizeof buf, "%.1f days", s.count() / (24 * 60 * 60)); + + std::string result = buf; + + if (show_positive && s > 0s) + result = "+" + result; + + return result; + } + + + std::string + tz_offset_to_string(minutes offset) + { + char buf[32]; + char sign = offset < 0min ? '-' : '+'; + int hours = std::abs(offset.count() / 60); + int minutes = std::abs(offset.count() % 60); + std::snprintf(buf, sizeof buf, "%c%02d:%02d", sign, hours, minutes); + return buf; + } + +} // namespace time_utils diff --git a/source/timezone_offset_item.cpp b/source/timezone_offset_item.cpp index 5f3d9d0..e3667a5 100644 --- a/source/timezone_offset_item.cpp +++ b/source/timezone_offset_item.cpp @@ -37,7 +37,7 @@ int timezone_offset_item::get_display(char* buf, std::size_t size) const { - auto str = utils::tz_offset_to_string(*variable); + auto str = time_utils::tz_offset_to_string(*variable); ::strlcpy(buf, str.c_str(), size); return 0; } @@ -59,7 +59,7 @@ timezone_offset_item::get_selected_display(char* buf, std::size_t size) fast_right = NIN_GLYPH_BTN_R; } - auto str = utils::tz_offset_to_string(*variable); + auto str = time_utils::tz_offset_to_string(*variable); std::snprintf(buf, size, "%s%s" "%s" "%s%s", fast_left, slow_left, str.c_str(), @@ -113,7 +113,7 @@ timezone_offset_item::on_changed() return; try { - wups::storage::store(*key, variable->count()); + wups::storage::store(*key, *variable); variable.reset(); } catch (std::exception& e) { diff --git a/source/timezone_query_item.cpp b/source/timezone_query_item.cpp index 9434aaa..5fd9e95 100644 --- a/source/timezone_query_item.cpp +++ b/source/timezone_query_item.cpp @@ -42,7 +42,7 @@ timezone_query_item::run() try { auto [name, offset] = utils::fetch_timezone(); text = name; - cfg::set_utc_offset(offset); + cfg::set_and_store_utc_offset(offset); } catch (std::exception& e) { text = "Error: "s + e.what(); diff --git a/source/utc.cpp b/source/utc.cpp index 0d164d2..a180d2c 100644 --- a/source/utc.cpp +++ b/source/utc.cpp @@ -10,10 +10,11 @@ namespace utc { static - double + dbl_seconds local_time() { - return static_cast(OSGetTime()) / OSTimerClockSpeed; + double t = static_cast(OSGetTime()) / OSTimerClockSpeed; + return dbl_seconds{t}; } @@ -21,8 +22,7 @@ namespace utc { now() noexcept { - auto offset_seconds = duration_cast(cfg::get_utc_offset()); - return timestamp{ local_time() - offset_seconds.count() }; + return timestamp{ local_time() - cfg::utc_offset }; } } // namespace utc diff --git a/source/utils.cpp b/source/utils.cpp index 2a9babd..fcae7cb 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -1,7 +1,5 @@ // SPDX-License-Identifier: MIT -#include // abs() -#include // snprintf() #include // runtime_error #include "utils.hpp" @@ -14,31 +12,6 @@ using namespace std::literals; namespace utils { - std::string - seconds_to_human(double s, bool show_positive) - { - char buf[64]; - - if (std::abs(s) < 2) // less than 2 seconds - std::snprintf(buf, sizeof buf, "%.1f ms", 1000 * s); - else if (std::abs(s) < 2 * 60) // less than 2 minutes - std::snprintf(buf, sizeof buf, "%.1f s", s); - else if (std::abs(s) < 2 * 60 * 60) // less than 2 hours - std::snprintf(buf, sizeof buf, "%.1f min", s / 60); - else if (std::abs(s) < 2 * 24 * 60 * 60) // less than 2 days - std::snprintf(buf, sizeof buf, "%.1f hrs", s / (60 * 60)); - else - std::snprintf(buf, sizeof buf, "%.1f days", s / (24 * 60 * 60)); - - std::string result = buf; - - if (show_positive && s > 0) - result = "+" + result; - - return result; - } - - std::vector split(const std::string& input, const std::string& separators, @@ -96,16 +69,4 @@ namespace utils { return {tokens[0], std::chrono::minutes{tz_offset_min}}; } - - std::string - tz_offset_to_string(std::chrono::minutes offset) - { - char buf[32]; - char sign = offset < 0min ? '-' : '+'; - int hours = std::abs(offset.count() / 60); - int minutes = std::abs(offset.count() % 60); - std::snprintf(buf, sizeof buf, "%c%02d:%02d", sign, hours, minutes); - return buf; - } - } // namespace utils diff --git a/source/verbosity_item.cpp b/source/verbosity_item.cpp index 282b613..024d6a7 100644 --- a/source/verbosity_item.cpp +++ b/source/verbosity_item.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT #include // clamp() +#include // BSD strlcpy() #include "verbosity_item.hpp" @@ -27,9 +28,9 @@ verbosity_item::verbosity_item(const std::string& key, const std::string& label, int& variable, int default_value) : - int_item{key, label, - variable, default_value, - 0, 2, 2} + wups::config::int_item{key, label, + variable, default_value, + 0, 2, 2} {} @@ -47,7 +48,7 @@ int verbosity_item::get_display(char* buf, std::size_t size) const { - std::snprintf(buf, size, "%s", value_to_str(*variable)); + ::strlcpy(buf, value_to_str(*variable), size); return 0; } diff --git a/source/wupsxx/duration_item.cpp b/source/wupsxx/duration_item.cpp deleted file mode 100644 index 9f590f4..0000000 --- a/source/wupsxx/duration_item.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MIT - -#include // clamp() -#include -#include - -#include "wupsxx/duration_item.hpp" - -#include "logging.hpp" -#include "nintendo_glyphs.h" -#include "wupsxx/storage.hpp" - - -namespace wups::config { - - duration_item::duration_item(const std::optional& key, - const std::string& label, - std::chrono::milliseconds& variable, std::chrono::milliseconds default_value, - std::chrono::milliseconds min_value, std::chrono::milliseconds max_value, - std::chrono::milliseconds fast_increment, std::chrono::milliseconds slow_increment) : - item{key, label}, - variable(variable), - default_value{default_value}, - min_value{min_value}, - max_value{max_value}, - fast_increment{fast_increment}, - slow_increment{slow_increment} - {} - - - std::unique_ptr - duration_item::create(const std::optional& key, - const std::string& label, - std::chrono::milliseconds& variable, std::chrono::milliseconds default_value, - std::chrono::milliseconds min_value, std::chrono::milliseconds max_value, - std::chrono::milliseconds fast_increment, std::chrono::milliseconds slow_increment) - { - return std::make_unique(key, label, - variable, default_value, - min_value, max_value, - fast_increment, slow_increment); - } - - - int - duration_item::get_display(char* buf, std::size_t size) - const - { - std::snprintf(buf, size, "%lld", variable->count()); - return 0; - } - - - int - duration_item::get_selected_display(char* buf, std::size_t size) - const - { - const char* slow_left = ""; - const char* slow_right = ""; - const char* fast_left = ""; - const char* fast_right = ""; - if (variable > min_value) { - slow_left = NIN_GLYPH_BTN_DPAD_LEFT " "; - fast_left = NIN_GLYPH_BTN_L; - } if (variable < max_value) { - slow_right = " " NIN_GLYPH_BTN_DPAD_RIGHT; - fast_right = NIN_GLYPH_BTN_R; - } - std::snprintf(buf, size, - "%s%s%lld%s%s", - fast_left, - slow_left, - variable->count(), - slow_right, - fast_right); - return 0; - } - - - void - duration_item::restore() - { - variable = default_value; - on_changed(); - } - - - void - duration_item::on_input(WUPSConfigSimplePadData input, - WUPS_CONFIG_SIMPLE_INPUT repeat) - { - item::on_input(input, repeat); - - if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT || - repeat & WUPS_CONFIG_BUTTON_LEFT) - variable -= slow_increment; - - if (input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT || - repeat & WUPS_CONFIG_BUTTON_RIGHT) - variable += slow_increment; - - if (input.buttons_d & WUPS_CONFIG_BUTTON_L || - repeat & WUPS_CONFIG_BUTTON_L) - variable -= fast_increment; - - if (input.buttons_d & WUPS_CONFIG_BUTTON_R || - repeat & WUPS_CONFIG_BUTTON_R) - variable += fast_increment; - - variable = std::clamp(*variable, min_value, max_value); - - on_changed(); - } - - - void - duration_item::on_changed() - { - if (!key) - return; - if (!variable.changed()) - return; - - try { - storage::store(*key, variable->count()); - variable.reset(); - } - catch (std::exception& e) { - logging::printf("Error storing duration: %s", e.what()); - } - } - -} // namespace wups::config \ No newline at end of file diff --git a/source/wupsxx/duration_items.cpp b/source/wupsxx/duration_items.cpp new file mode 100644 index 0000000..21b2e8a --- /dev/null +++ b/source/wupsxx/duration_items.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +#include "numeric_item_impl.hpp" + +namespace wups::config { + + template class numeric_item; + + template class numeric_item; + +} // namespace wups::config diff --git a/source/wupsxx/int_item.cpp b/source/wupsxx/int_item.cpp index 0da5f25..dc4eb24 100644 --- a/source/wupsxx/int_item.cpp +++ b/source/wupsxx/int_item.cpp @@ -1,133 +1,6 @@ // SPDX-License-Identifier: MIT -#include // clamp() -#include -#include +#include "numeric_item_impl.hpp" -#include "wupsxx/int_item.hpp" -#include "logging.hpp" -#include "nintendo_glyphs.h" -#include "wupsxx/storage.hpp" - - -namespace wups::config { - - int_item::int_item(const std::optional& key, - const std::string& label, - int& variable, int default_value, - int min_value, int max_value, - int fast_increment, int slow_increment) : - item{key, label}, - variable(variable), - default_value{default_value}, - min_value{min_value}, - max_value{max_value}, - fast_increment{fast_increment}, - slow_increment{slow_increment} - {} - - - std::unique_ptr - int_item::create(const std::optional& key, - const std::string& label, - int& variable, int default_value, - int min_value, int max_value, - int fast_increment, int slow_increment) - { - return std::make_unique(key, label, - variable, default_value, - min_value, max_value, - fast_increment, slow_increment); - } - - - int - int_item::get_display(char* buf, std::size_t size) - const - { - std::snprintf(buf, size, "%d", *variable); - return 0; - } - - - int - int_item::get_selected_display(char* buf, std::size_t size) - const - { - const char* slow_left = ""; - const char* slow_right = ""; - const char* fast_left = ""; - const char* fast_right = ""; - if (*variable > min_value) { - slow_left = NIN_GLYPH_BTN_DPAD_LEFT " "; - fast_left = NIN_GLYPH_BTN_L; - } if (*variable < max_value) { - slow_right = " " NIN_GLYPH_BTN_DPAD_RIGHT; - fast_right = NIN_GLYPH_BTN_R; - } - std::snprintf(buf, size, - "%s%s%d%s%s", - fast_left, - slow_left, - *variable, - slow_right, - fast_right); - return 0; - } - - - void - int_item::restore() - { - variable = default_value; - on_changed(); - } - - - void - int_item::on_input(WUPSConfigSimplePadData input, - WUPS_CONFIG_SIMPLE_INPUT repeat) - { - item::on_input(input, repeat); - - if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT || - repeat & WUPS_CONFIG_BUTTON_LEFT) - variable -= slow_increment; - - if (input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT || - repeat & WUPS_CONFIG_BUTTON_RIGHT) - variable += slow_increment; - - if (input.buttons_d & WUPS_CONFIG_BUTTON_L || - repeat & WUPS_CONFIG_BUTTON_L) - variable -= fast_increment; - - if (input.buttons_d & WUPS_CONFIG_BUTTON_R || - repeat & WUPS_CONFIG_BUTTON_R) - variable += fast_increment; - - variable = std::clamp(*variable, min_value, max_value); - - on_changed(); - } - - - void - int_item::on_changed() - { - if (!key) - return; - if (!variable.changed()) - return; - - try { - storage::store(*key, *variable); - variable.reset(); - } - catch (std::exception& e) { - logging::printf("Error storing int: %s", e.what()); - } - } - -} // namespace wups::config +template class wups::config::numeric_item; diff --git a/source/wupsxx/numeric_item_impl.hpp b/source/wupsxx/numeric_item_impl.hpp new file mode 100644 index 0000000..f65e8ac --- /dev/null +++ b/source/wupsxx/numeric_item_impl.hpp @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT + +#ifndef WUPSXX_NUMERIC_ITEM_IMPL_HPP +#define WUPSXX_NUMERIC_ITEM_IMPL_HPP + +#include // clamp() +#include +#include // snprintf() +#include +#include // BSD strlcpy() + +#include "wupsxx/numeric_item.hpp" + +#include "logging.hpp" +#include "nintendo_glyphs.h" +#include "wupsxx/storage.hpp" +#include "time_utils.hpp" + + +using std::to_string; +using time_utils::to_string; + + +namespace wups::config { + + template + numeric_item::numeric_item(const std::optional& key, + const std::string& label, + T& variable, T default_value, + T min_value, T max_value, + T fast_increment, T slow_increment) : + item{key, label}, + variable(variable), + default_value{default_value}, + min_value{min_value}, + max_value{max_value}, + fast_increment{fast_increment}, + slow_increment{slow_increment} + {} + + + template + std::unique_ptr> + numeric_item::create(const std::optional& key, + const std::string& label, + T& variable, T default_value, + T min_value, T max_value, + T fast_increment, T slow_increment) + { + return std::make_unique>(key, label, + variable, default_value, + min_value, max_value, + fast_increment, slow_increment); + } + + + template + int + numeric_item::get_display(char* buf, std::size_t size) + const + { + std::string str = to_string(*variable); + ::strlcpy(buf, str.c_str(), size); + return 0; + } + + + template + int + numeric_item::get_selected_display(char* buf, std::size_t size) + const + { + const char* slow_left = ""; + const char* slow_right = ""; + const char* fast_left = ""; + const char* fast_right = ""; + if (*variable > min_value) { + slow_left = NIN_GLYPH_BTN_DPAD_LEFT " "; + fast_left = NIN_GLYPH_BTN_L; + } if (*variable < max_value) { + slow_right = " " NIN_GLYPH_BTN_DPAD_RIGHT; + fast_right = NIN_GLYPH_BTN_R; + } + std::string str = to_string(*variable); + std::snprintf(buf, size, + "%s%s" "%s" "%s%s", + fast_left, + slow_left, + str.c_str(), + slow_right, + fast_right); + return 0; + } + + + template + void + numeric_item::restore() + { + variable = default_value; + on_changed(); + } + + + template + void + numeric_item::on_input(WUPSConfigSimplePadData input, + WUPS_CONFIG_SIMPLE_INPUT repeat) + { + item::on_input(input, repeat); + + if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT || + repeat & WUPS_CONFIG_BUTTON_LEFT) + variable -= slow_increment; + + if (input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT || + repeat & WUPS_CONFIG_BUTTON_RIGHT) + variable += slow_increment; + + if (input.buttons_d & WUPS_CONFIG_BUTTON_L || + repeat & WUPS_CONFIG_BUTTON_L) + variable -= fast_increment; + + if (input.buttons_d & WUPS_CONFIG_BUTTON_R || + repeat & WUPS_CONFIG_BUTTON_R) + variable += fast_increment; + + variable = std::clamp(*variable, min_value, max_value); + + on_changed(); + } + + + template + void + numeric_item::on_changed() + { + if (!key) + return; + if (!variable.changed()) + return; + + try { + storage::store(*key, *variable); + variable.reset(); + } + catch (std::exception& e) { + logging::printf("Error storing %s: %s", key->c_str(), e.what()); + } + } + +} // namespace wups::config + + +#endif