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