aboutsummaryrefslogtreecommitdiff
path: root/src/any_map.hpp
blob: dfc54f3f006dd1fcec33e7f24bde6bf7d2799494 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#pragma once

// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"

#include <any>
#include <unordered_map>
#include <functional>
#include <optional>
#include <typeinfo>

#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<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;
    };
}