#pragma once #include #include #include #include #include #include "base.hpp" #include "format.hpp" namespace cru { // 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; 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 std::optional 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(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 T GetValue(const String& key) const { const auto optional_value = GetOptionalValue(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 void SetValue(const String& key, T&& value) { auto& p = map_[key]; p.first = std::make_any(std::forward(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& listener_list, const std::any& value); private: std::unordered_map>> map_{}; std::unordered_map listeners_{}; ListenerToken current_listener_token_ = 0; }; }