Mercurial > hg > svcore
comparison base/ById.h @ 1742:52705a328b34 by-id
Rejig ById so as to put everything in a single pool, so that at the core you can go from numeric id (untyped) to anything the object can be dynamic_cast to. Useful for building other abstractions like PlayParameter-type registrations that don't know about e.g. Models. Probably some more tweaking needed. Also add tests
author | Chris Cannam |
---|---|
date | Fri, 28 Jun 2019 17:36:30 +0100 |
parents | 565575463752 |
children | b92bdcd4954b |
comparison
equal
deleted
inserted
replaced
1741:9d82b164f264 | 1742:52705a328b34 |
---|---|
16 #define SV_BY_ID_H | 16 #define SV_BY_ID_H |
17 | 17 |
18 #include "Debug.h" | 18 #include "Debug.h" |
19 | 19 |
20 #include <memory> | 20 #include <memory> |
21 #include <map> | |
22 #include <typeinfo> | |
23 #include <iostream> | 21 #include <iostream> |
24 #include <climits> | 22 #include <climits> |
23 #include <stdexcept> | |
25 | 24 |
26 #include <QMutex> | 25 #include <QMutex> |
27 #include <QString> | 26 #include <QString> |
28 | 27 |
29 #include "XmlExportable.h" | 28 #include "XmlExportable.h" |
30 | 29 |
30 struct IdAlloc { | |
31 | |
32 // The value NO_ID (-1) is never allocated | |
33 static const int NO_ID = -1; | |
34 | |
35 static int getNextId(); | |
36 }; | |
37 | |
31 template <typename T> | 38 template <typename T> |
32 struct SvId { | 39 struct TypedId { |
33 | 40 |
34 int id; | 41 int untyped; |
42 | |
43 TypedId() : untyped(IdAlloc::NO_ID) {} | |
35 | 44 |
36 enum { | 45 TypedId(const TypedId &) =default; |
37 // The value NO_ID (-1) is never allocated by WithId | 46 TypedId &operator=(const TypedId &) =default; |
38 NO_ID = -1 | |
39 }; | |
40 | |
41 SvId() : id(NO_ID) {} | |
42 | 47 |
43 SvId(const SvId &) =default; | 48 bool operator==(const TypedId &other) const { |
44 SvId &operator=(const SvId &) =default; | 49 return untyped == other.untyped; |
45 | 50 } |
46 bool operator==(const SvId &other) const { return id == other.id; } | 51 bool operator!=(const TypedId &other) const { |
47 bool operator<(const SvId &other) const { return id < other.id; } | 52 return untyped != other.untyped; |
48 | 53 } |
49 bool isNone() const { return id == NO_ID; } | 54 bool operator<(const TypedId &other) const { |
50 | 55 return untyped < other.untyped; |
51 QString toString() const { | 56 } |
52 return QString("%1").arg(id); | 57 bool isNone() const { |
58 return untyped == IdAlloc::NO_ID; | |
53 } | 59 } |
54 }; | 60 }; |
55 | 61 |
56 template <typename T> | 62 template <typename T> |
57 std::ostream & | 63 std::ostream & |
58 operator<<(std::ostream &ostr, const SvId<T> &id) | 64 operator<<(std::ostream &ostr, const TypedId<T> &id) |
59 { | 65 { |
60 // For diagnostic purposes only. Do not use these IDs for | 66 // For diagnostic purposes only. Do not use these IDs for |
61 // serialisation - see XmlExportable instead. | 67 // serialisation - see XmlExportable instead. |
62 if (id.isNone()) { | 68 if (id.isNone()) { |
63 return (ostr << "<none>"); | 69 return (ostr << "<none>"); |
64 } else { | 70 } else { |
65 return (ostr << "#" << id.id); | 71 return (ostr << "#" << id.untyped); |
66 } | 72 } |
67 } | 73 } |
68 | 74 |
69 template <typename T> | |
70 class WithId | 75 class WithId |
71 { | 76 { |
72 public: | 77 public: |
73 typedef SvId<T> Id; | |
74 | |
75 WithId() : | 78 WithId() : |
76 m_id(getNextId()) { | 79 m_id(IdAlloc::getNextId()) { |
80 } | |
81 virtual ~WithId() { | |
77 } | 82 } |
78 | 83 |
79 /** | 84 /** |
80 * Return an id for this object. The id is a unique identifier for | 85 * Return an id for this object. The id is a unique number for |
81 * this object among all objects that implement WithId within this | 86 * this object among all objects that implement WithId within this |
87 * single run of the application. | |
88 */ | |
89 int getUntypedId() const { | |
90 return m_id; | |
91 } | |
92 | |
93 private: | |
94 int m_id; | |
95 }; | |
96 | |
97 template <typename T> | |
98 class WithTypedId : virtual public WithId | |
99 { | |
100 public: | |
101 typedef TypedId<T> Id; | |
102 | |
103 WithTypedId() : WithId() { } | |
104 | |
105 /** | |
106 * Return an id for this object. The id is a unique value for this | |
107 * object among all objects that implement WithTypedId within this | |
82 * single run of the application. | 108 * single run of the application. |
83 */ | 109 */ |
84 Id getId() const { | 110 Id getId() const { |
85 Id id; | 111 Id id; |
86 id.id = m_id; | 112 id.untyped = getUntypedId(); |
87 return id; | 113 return id; |
114 } | |
115 }; | |
116 | |
117 class AnyById | |
118 { | |
119 public: | |
120 static void add(int, std::shared_ptr<WithId>); | |
121 static void release(int); | |
122 static std::shared_ptr<WithId> get(int); | |
123 | |
124 template <typename Derived> | |
125 static std::shared_ptr<Derived> getAs(int id) { | |
126 std::shared_ptr<WithId> p = get(id); | |
127 return std::dynamic_pointer_cast<Derived>(p); | |
88 } | 128 } |
89 | 129 |
90 private: | 130 private: |
91 int m_id; | 131 class Impl; |
92 | 132 static Impl &impl(); |
93 static int getNextId() { | |
94 static int nextId = 0; | |
95 static QMutex mutex; | |
96 QMutexLocker locker(&mutex); | |
97 int i = nextId; | |
98 if (nextId == INT_MAX) { | |
99 nextId = INT_MIN; | |
100 } else { | |
101 ++nextId; | |
102 if (nextId == 0 || nextId == Id::NO_ID) { | |
103 throw std::runtime_error("Internal ID limit exceeded!"); | |
104 } | |
105 } | |
106 return i; | |
107 } | |
108 }; | 133 }; |
109 | 134 |
110 template <typename Item, typename Id> | 135 template <typename Item, typename Id> |
111 class ById | 136 class TypedById |
112 { | 137 { |
113 public: | 138 public: |
114 ~ById() { | 139 static void add(std::shared_ptr<Item> item) { |
115 QMutexLocker locker(&m_mutex); | |
116 for (const auto &p: m_items) { | |
117 if (p.second && p.second.use_count() > 0) { | |
118 SVCERR << "WARNING: ById map destroyed with use count of " | |
119 << p.second.use_count() << " for item with type " | |
120 << typeid(*p.second.get()).name() | |
121 << " and id " << p.first.id << endl; | |
122 } | |
123 } | |
124 } | |
125 | |
126 void add(std::shared_ptr<Item> item) { | |
127 QMutexLocker locker(&m_mutex); | |
128 auto id = item->getId(); | 140 auto id = item->getId(); |
129 if (id.isNone()) { | 141 if (id.isNone()) { |
130 throw std::logic_error("item id should never be None"); | 142 throw std::logic_error("item id should never be None"); |
131 } | 143 } |
132 if (m_items.find(id) != m_items.end()) { | 144 AnyById::add(id.untyped, item); |
133 SVCERR << "WARNING: ById::add: item with id " << id | |
134 << " is already recorded, replacing it (item type is " | |
135 << typeid(*item.get()).name() << ")" << endl; | |
136 } | |
137 m_items[id] = item; | |
138 } | 145 } |
139 | 146 |
140 void | 147 static void release(Id id) { |
141 release(Id id) { | 148 AnyById::release(id.untyped); |
142 QMutexLocker locker(&m_mutex); | |
143 m_items.erase(id); | |
144 } | 149 } |
145 | 150 static void release(std::shared_ptr<Item> item) { |
146 std::shared_ptr<Item> get(Id id) const { | 151 release(item->getId()); |
147 if (id.isNone()) return {}; // this id is never issued: avoid locking | |
148 QMutexLocker locker(&m_mutex); | |
149 const auto &itr = m_items.find(id); | |
150 if (itr != m_items.end()) { | |
151 return itr->second; | |
152 } else { | |
153 return {}; | |
154 } | |
155 } | 152 } |
156 | 153 |
157 template <typename Derived> | 154 template <typename Derived> |
158 std::shared_ptr<Derived> getAs(Id id) const { | 155 static std::shared_ptr<Derived> getAs(Id id) { |
159 return std::dynamic_pointer_cast<Derived>(get(id)); | 156 if (id.isNone()) return {}; // this id is never issued: avoid locking |
157 return AnyById::getAs<Derived>(id.untyped); | |
158 } | |
159 | |
160 static std::shared_ptr<Item> get(Id id) { | |
161 return getAs<Item>(id); | |
160 } | 162 } |
161 | 163 |
162 /** | 164 /** |
163 * If the Item type is an XmlExportable, return the export ID of | 165 * If the Item type is an XmlExportable, return the export ID of |
164 * the given item ID. A call to this function will fail to compile | 166 * the given item ID. A call to this function will fail to compile |
165 * if the Item is not an XmlExportable. | 167 * if the Item is not an XmlExportable. |
166 * | 168 * |
167 * The export ID is a simple int, and is only allocated when first | 169 * The export ID is a simple int, and is only allocated when first |
168 * requested, so objects that are never exported don't get one. | 170 * requested, so objects that are never exported don't get one. |
169 */ | 171 */ |
170 int getExportId(Id id) const { | 172 static int getExportId(Id id) { |
171 auto exportable = getAs<XmlExportable>(id); | 173 auto exportable = getAs<XmlExportable>(id); |
172 if (exportable) { | 174 if (exportable) { |
173 return exportable->getExportId(); | 175 return exportable->getExportId(); |
174 } else { | 176 } else { |
175 return XmlExportable::NO_ID; | 177 return XmlExportable::NO_ID; |
176 } | 178 } |
177 } | 179 } |
178 | |
179 private: | |
180 mutable QMutex m_mutex; | |
181 std::map<Id, std::shared_ptr<Item>> m_items; | |
182 }; | |
183 | |
184 template <typename Item, typename Id> | |
185 class StaticById | |
186 { | |
187 public: | |
188 static void add(std::shared_ptr<Item> imagined) { | |
189 byId().add(imagined); | |
190 } | |
191 | |
192 static void release(Id id) { | |
193 byId().release(id); | |
194 } | |
195 | |
196 static std::shared_ptr<Item> get(Id id) { | |
197 return byId().get(id); | |
198 } | |
199 | |
200 template <typename Derived> | |
201 static | |
202 std::shared_ptr<Derived> getAs(Id id) { | |
203 return std::dynamic_pointer_cast<Derived>(get(id)); | |
204 } | |
205 | |
206 static int getExportId(Id id) { | |
207 return byId().getExportId(id); | |
208 } | |
209 | |
210 private: | |
211 static | |
212 ById<Item, Id> &byId() { | |
213 static ById<Item, Id> b; | |
214 return b; | |
215 } | |
216 }; | 180 }; |
217 | 181 |
218 #endif | 182 #endif |
219 | 183 |