aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/any_map.cpp30
-rw-r--r--src/util/any_map.hpp94
-rw-r--r--src/util/format.hpp94
-rw-r--r--src/util/math_util.hpp51
-rw-r--r--src/util/string_util.cpp21
-rw-r--r--src/util/string_util.hpp8
6 files changed, 298 insertions, 0 deletions
diff --git a/src/util/any_map.cpp b/src/util/any_map.cpp
new file mode 100644
index 00000000..c49464d3
--- /dev/null
+++ b/src/util/any_map.cpp
@@ -0,0 +1,30 @@
+#include "any_map.hpp"
+
+namespace cru::util {
+AnyMap::ListenerToken AnyMap::RegisterValueChangeListener(
+ const String& key, const Listener& listener) {
+ const auto token = current_listener_token_++;
+ map_[key].second.push_back(token);
+ listeners_.emplace(token, listener);
+ return token;
+}
+
+void AnyMap::UnregisterValueChangeListener(const ListenerToken token) {
+ const auto find_result = listeners_.find(token);
+ if (find_result != listeners_.cend()) listeners_.erase(find_result);
+}
+
+void AnyMap::InvokeListeners(std::list<ListenerToken>& listener_list,
+ const std::any& value) {
+ auto i = listener_list.cbegin();
+ while (i != listener_list.cend()) {
+ auto current_i = i++;
+ const auto find_result = listeners_.find(*current_i);
+ if (find_result != listeners_.cend())
+ find_result->second(value);
+ else
+ listener_list.erase(
+ current_i); // otherwise remove the invalid listener token.
+ }
+}
+} // namespace cru::util
diff --git a/src/util/any_map.hpp b/src/util/any_map.hpp
new file mode 100644
index 00000000..d82167d2
--- /dev/null
+++ b/src/util/any_map.hpp
@@ -0,0 +1,94 @@
+#pragma once
+#include "pre.hpp"
+
+#include <any>
+#include <functional>
+#include <optional>
+#include <typeinfo>
+#include <unordered_map>
+
+#include "base.hpp"
+#include "format.hpp"
+
+namespace cru::util {
+// A map with String as key and any type as value.
+// It also has notification when value with specified key changed.
+class AnyMap : public Object {
+ public:
+ using ListenerToken = long;
+ using Listener = std::function<void(const std::any&)>;
+
+ AnyMap() = default;
+ AnyMap(const AnyMap& other) = delete;
+ AnyMap(AnyMap&& other) = delete;
+ AnyMap& operator=(const AnyMap& other) = delete;
+ AnyMap& operator=(AnyMap&& other) = delete;
+ ~AnyMap() override = default;
+
+ // return the value if the value exists and the type of value is T.
+ // return a null optional if value doesn't exists.
+ // throw std::runtime_error if type is mismatch.
+ template <typename T>
+ std::optional<T> GetOptionalValue(const String& key) const {
+ try {
+ const auto find_result = map_.find(key);
+ if (find_result != map_.cend()) {
+ const auto& value = find_result->second.first;
+ if (value.has_value()) return std::any_cast<T>(value);
+ return std::nullopt;
+ }
+ return std::nullopt;
+ } catch (const std::bad_any_cast&) {
+ throw std::runtime_error(
+ Format("Value of key \"{}\" in AnyMap is not of the type {}.",
+ ToUtf8String(key), typeid(T).name()));
+ }
+ }
+
+ // return the value if the value exists and the type of value is T.
+ // throw if value doesn't exists. (different from "GetOptionalValue").
+ // throw std::runtime_error if type is mismatch.
+ template <typename T>
+ T GetValue(const String& key) const {
+ const auto optional_value = GetOptionalValue<T>(key);
+ if (optional_value.has_value())
+ return optional_value.value();
+ else
+ throw std::runtime_error(
+ Format("Key \"{}\" does not exists in AnyMap.", ToUtf8String(key)));
+ }
+
+ // Set the value of key, and trigger all related listeners.
+ template <typename T>
+ void SetValue(const String& key, T&& value) {
+ auto& p = map_[key];
+ p.first = std::make_any<T>(std::forward<T>(value));
+ InvokeListeners(p.second, p.first);
+ }
+
+ // Remove the value of the key.
+ void ClearValue(const String& key) {
+ auto& p = map_[key];
+ p.first = std::any{};
+ InvokeListeners(p.second, std::any{});
+ }
+
+ // Add a listener which is called when value of key is changed.
+ // Return a token used to remove the listener.
+ ListenerToken RegisterValueChangeListener(const String& key,
+ const Listener& listener);
+
+ // Remove a listener by token.
+ void UnregisterValueChangeListener(ListenerToken token);
+
+ private:
+ void InvokeListeners(std::list<ListenerToken>& listener_list,
+ const std::any& value);
+
+ private:
+ std::unordered_map<String, std::pair<std::any, std::list<ListenerToken>>>
+ map_{};
+ std::unordered_map<ListenerToken, Listener> listeners_{};
+ ListenerToken current_listener_token_ = 0;
+};
+} // namespace cru::util
diff --git a/src/util/format.hpp b/src/util/format.hpp
new file mode 100644
index 00000000..874c5b43
--- /dev/null
+++ b/src/util/format.hpp
@@ -0,0 +1,94 @@
+#pragma once
+#include "pre.hpp"
+
+#include "base.hpp"
+
+namespace cru::util {
+namespace details {
+constexpr StringView PlaceHolder(type_tag<String>) { return StringView(L"{}"); }
+
+constexpr MultiByteStringView PlaceHolder(type_tag<MultiByteString>) {
+ return MultiByteStringView("{}");
+}
+
+template <typename TString>
+void FormatInternal(TString& string) {
+ const auto find_result = string.find(PlaceHolder(type_tag<TString>{}));
+ if (find_result != TString::npos)
+ throw std::invalid_argument("There is more placeholders than args.");
+}
+
+template <typename TString, typename T, typename... TRest>
+void FormatInternal(TString& string, const T& arg, const TRest&... args) {
+ const auto find_result = string.find(PlaceHolder(type_tag<TString>{}));
+ if (find_result == TString::npos)
+ throw std::invalid_argument("There is less placeholders than args.");
+
+ string.replace(find_result, 2, FormatToString(arg, type_tag<TString>{}));
+ FormatInternal<TString>(string, args...);
+}
+} // namespace details
+
+template <typename... T>
+String Format(const StringView& format, const T&... args) {
+ String result(format);
+ details::FormatInternal<String>(result, args...);
+ return result;
+}
+
+template <typename... T>
+MultiByteString Format(const MultiByteStringView& format, const T&... args) {
+ MultiByteString result(format);
+ details::FormatInternal<MultiByteString>(result, args...);
+ return result;
+}
+
+#define CRU_FORMAT_NUMBER(type) \
+ inline String FormatToString(const type number, type_tag<String>) { \
+ return std::to_wstring(number); \
+ } \
+ inline MultiByteString FormatToString(const type number, \
+ type_tag<MultiByteString>) { \
+ return std::to_string(number); \
+ }
+
+CRU_FORMAT_NUMBER(int)
+CRU_FORMAT_NUMBER(short)
+CRU_FORMAT_NUMBER(long)
+CRU_FORMAT_NUMBER(long long)
+CRU_FORMAT_NUMBER(unsigned int)
+CRU_FORMAT_NUMBER(unsigned short)
+CRU_FORMAT_NUMBER(unsigned long)
+CRU_FORMAT_NUMBER(unsigned long long)
+CRU_FORMAT_NUMBER(float)
+CRU_FORMAT_NUMBER(double)
+
+#undef CRU_FORMAT_NUMBER
+
+inline StringView FormatToString(const String& string, type_tag<String>) {
+ return string;
+}
+
+inline MultiByteString FormatToString(const MultiByteString& string,
+ type_tag<MultiByteString>) {
+ return string;
+}
+
+inline StringView FormatToString(const StringView& string, type_tag<String>) {
+ return string;
+}
+
+inline MultiByteStringView FormatToString(const MultiByteStringView& string,
+ type_tag<MultiByteString>) {
+ return string;
+}
+
+inline StringView FormatToString(const wchar_t* string, type_tag<String>) {
+ return StringView(string);
+}
+
+inline MultiByteStringView FormatToString(const char* string,
+ type_tag<MultiByteString>) {
+ return MultiByteString(string);
+}
+} // namespace cru::util
diff --git a/src/util/math_util.hpp b/src/util/math_util.hpp
new file mode 100644
index 00000000..01348641
--- /dev/null
+++ b/src/util/math_util.hpp
@@ -0,0 +1,51 @@
+#pragma once
+#include "pre.hpp"
+
+#include <optional>
+#include <type_traits>
+
+namespace cru::util {
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::optional<T> min,
+ const std::optional<T> max) {
+ if (min.has_value() && n < min.value()) return min.value();
+ if (max.has_value() && n > max.value()) return max.value();
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const T min, const T max) {
+ if (n < min) return min;
+ if (n > max) return max;
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::nullopt_t, const std::optional<T> max) {
+ if (max.has_value() && n > max.value()) return max.value();
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::optional<T> min, const std::nullopt_t) {
+ if (min.has_value() && n < min.value()) return min.value();
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::nullopt_t, const T max) {
+ if (n > max) return max;
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const T min, const std::nullopt_t) {
+ if (n < min) return min;
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+T AtLeast0(const T value) {
+ return value < static_cast<T>(0) ? static_cast<T>(0) : value;
+}
+} // namespace cru::util
diff --git a/src/util/string_util.cpp b/src/util/string_util.cpp
new file mode 100644
index 00000000..3c765259
--- /dev/null
+++ b/src/util/string_util.cpp
@@ -0,0 +1,21 @@
+#include "string_util.hpp"
+
+#include "system_headers.hpp"
+#include "exception.hpp"
+
+namespace cru::util {
+MultiByteString ToUtf8String(const StringView& string) {
+ if (string.empty()) return MultiByteString();
+
+ const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1,
+ nullptr, 0, nullptr, nullptr);
+ MultiByteString result;
+ result.reserve(length);
+ if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(),
+ static_cast<int>(result.capacity()), nullptr,
+ nullptr) == 0)
+ throw Win32Error(::GetLastError(),
+ "Failed to convert wide string to UTF-8.");
+ return result;
+}
+}
diff --git a/src/util/string_util.hpp b/src/util/string_util.hpp
new file mode 100644
index 00000000..6d060089
--- /dev/null
+++ b/src/util/string_util.hpp
@@ -0,0 +1,8 @@
+#pragma once
+#include "pre.hpp"
+
+#include "base.hpp"
+
+namespace cru::util {
+MultiByteString ToUtf8String(const StringView& string);
+}