summaryrefslogtreecommitdiff
path: root/mm/2s2h/ObjectExtension/ObjectExtension.h
blob: 89a8dde90ee0976abad10f0125721fed097f760a (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
104
105
106
107
108
109
110
111
112
113
114
115
116
#pragma once

#ifdef __cplusplus

#include <any>
#include <cassert>
#include <limits>
#include <stdint.h>
#include <unordered_map>

/*
 * This class can attach additional data to pointers. It can only attach a single instance of each type of data.
 * Use the ObjectExtension::Register class to register a type to be used as an object extension.
 * An example usage is:
 *
 * struct MyData {
 *     s32 data = -1;
 * };
 * static ObjectExtension::Register<MyData> MyDataRegister;
 *
 * Then you can get with
 * ObjectExtension::GetInstance().Get<MyData>(ptr);
 * and set with
 * ObjectExtension::GetInstance().Set<MyData>(ptr, MyData{});
 * (or with the returned pointer from Get()).
 */
class ObjectExtension {
  public:
    using Id = uint32_t;

    static constexpr Id InvalidId = std::numeric_limits<Id>::max();

    // Registers type T to be used as an object extension
    template <typename T> class Register {
      public:
        Register() {
            Id = ObjectExtension::GetInstance().RegisterId();
        }

        static ObjectExtension::Id Id;
    };

    // Gets the singleton ObjectExtension instance
    static ObjectExtension& GetInstance();

    // Gets the data of type T associated with an object, or nullptr if no such data has been attached
    template <typename T> T* Get(const void* object) {
        assert(ObjectExtension::Register<T>::Id != InvalidId);
        if (object == nullptr) {
            return nullptr;
        }

        auto it = Data.find(std::make_pair(object, ObjectExtension::Register<T>::Id));
        if (it == Data.end()) {
            return nullptr;
        }

        return std::any_cast<T>(&(it->second));
    }

    // Sets the data of type T for an object. Data will be copied.
    template <typename T> void Set(const void* object, const T&& data) {
        assert(ObjectExtension::Register<T>::Id != InvalidId);
        if (object != nullptr) {
            Data[std::make_pair(object, ObjectExtension::Register<T>::Id)] = data;
        }
    }

    // Returns true if an object has data of type T associated with it
    template <typename T> bool Has(const void* object) {
        assert(ObjectExtension::Register<T>::Id != InvalidId);
        if (object == nullptr) {
            return false;
        }

        return Data.contains(std::make_pair(object, ObjectExtension::Register<T>::Id));
    }

    // Removes data of type T from an object
    template <typename T> void Remove(const void* object) {
        assert(ObjectExtension::Register<T>::Id != InvalidId);

        Data.erase(std::make_pair(object, ObjectExtension::Register<T>::Id));
    }

    // Removes all data from an object
    void Free(const void* object);

  private:
    ObjectExtension() = default;

    // Returns the next free object extension Id
    Id RegisterId();

    ObjectExtension::Id NextId = 0;

    struct KeyHash {
        std::size_t operator()(const std::pair<const void*, ObjectExtension::Id>& key) const {
            return std::hash<const void*>{}(key.first) ^ (std::hash<ObjectExtension::Id>{}(key.second) << 1);
        }
    };

    // Collection of all object extension data.
    std::unordered_map<std::pair<const void*, ObjectExtension::Id>, std::any, KeyHash> Data;
};

// Static template globals
template <typename T> ObjectExtension::Id ObjectExtension::Register<T>::Id = ObjectExtension::InvalidId;

extern "C" {
#endif // __cplusplus

void ObjectExtension_Free(const void* object);
#ifdef __cplusplus
}
#endif