annotate base/ById.h @ 1879:652c5360e682

Ensure transforms are populated before instantiateDefaultPluginFor runs - otherwise if we have prior knowledge of a transform id, we can find ourselves trying to instantiate it before the plugin factory has heard of it and e.g. knows which server to use
author Chris Cannam
date Thu, 25 Jun 2020 12:20:06 +0100
parents 7faa08747f5e
children
rev   line source
Chris@1729 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1729 2
Chris@1729 3 /*
Chris@1729 4 Sonic Visualiser
Chris@1729 5 An audio file viewer and annotation editor.
Chris@1729 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1729 7
Chris@1729 8 This program is free software; you can redistribute it and/or
Chris@1729 9 modify it under the terms of the GNU General Public License as
Chris@1729 10 published by the Free Software Foundation; either version 2 of the
Chris@1729 11 License, or (at your option) any later version. See the file
Chris@1729 12 COPYING included with this distribution for more information.
Chris@1729 13 */
Chris@1729 14
Chris@1729 15 #ifndef SV_BY_ID_H
Chris@1729 16 #define SV_BY_ID_H
Chris@1729 17
Chris@1734 18 #include "Debug.h"
Chris@1734 19
Chris@1729 20 #include <memory>
Chris@1731 21 #include <iostream>
Chris@1731 22 #include <climits>
Chris@1742 23 #include <stdexcept>
Chris@1729 24
Chris@1729 25 #include <QMutex>
Chris@1731 26 #include <QString>
Chris@1729 27
Chris@1738 28 #include "XmlExportable.h"
Chris@1738 29
Chris@1764 30 /*
Chris@1764 31 * ById - central pool of objects to be retrieved by persistent id.
Chris@1764 32 *
Chris@1764 33 * This is a pretty simple mechanism for obtaining safe "borrowed"
Chris@1764 34 * references to shared objects, including across threads, based on an
Chris@1764 35 * object ID.
Chris@1764 36 *
Chris@1764 37 * A class (call it C) inherits WithTypedId<C>. This produces a type
Chris@1764 38 * C::Id containing a numerical id. Each instance of C (or subclass
Chris@1764 39 * thereof) has an internal id of type C::Id whose value is unique
Chris@1764 40 * among all ids ever possessed by any instances of all classes that
Chris@1764 41 * use this id mechanism (within a single run of the program).
Chris@1764 42 *
Chris@1764 43 * Then we have a static store of type TypedById<C, C::Id>. This holds
Chris@1764 44 * a set of heap-allocated C objects (or subclass thereof) and hands
Chris@1764 45 * out shared_ptr references to them when queried by id. The
Chris@1764 46 * application calls add() to pass an object to the store (which takes
Chris@1764 47 * ownership of it), and the application calls release() when it
Chris@1764 48 * thinks it has finished with an object, to request the store to
Chris@1764 49 * delete it.
Chris@1764 50 *
Chris@1764 51 * Note that an object's id can't (without shenanigans) be queried
Chris@1764 52 * directly from that object - it is returned when the object is added
Chris@1764 53 * to a ById store. So if you have an object id, you know that the
Chris@1764 54 * object must have been added to a store at some point.
Chris@1764 55 *
Chris@1764 56 * The goal is to improve code that would previously have retained a
Chris@1764 57 * bare pointer to a heap-allocated object that it did not own. For
Chris@1764 58 * example, in Sonic Visualiser we have a Model hierarchy of complex
Chris@1764 59 * mutable objects, and any given model may be referred to by many
Chris@1764 60 * different layers, transforms (as both source and target) etc. Using
Chris@1764 61 * bare pointers for those references means that we need everything to
Chris@1764 62 * be notified (and act properly on the notification) if a model is
Chris@1764 63 * about to be deleted. Using a Model::Id instead gives the code a
Chris@1764 64 * guarantee: if the model has been deleted since you last looked at
Chris@1764 65 * it, then the ById store will return a null shared_ptr from its
Chris@1764 66 * get() function for that id; but if it returns a non-null
Chris@1764 67 * shared_ptr, then the object being pointed to can't be deleted while
Chris@1764 68 * that shared_ptr is in scope.
Chris@1764 69 *
Chris@1840 70 * The result is like using weak_ptr references to a shared_ptr whose
Chris@1840 71 * ownership is within a "layer" (at "application level") rather than
Chris@1840 72 * managed by a specific object. A human-readable id representation is
Chris@1840 73 * also quite useful for debugging.
Chris@1840 74 *
Chris@1764 75 * Example:
Chris@1764 76 *
Chris@1764 77 * class Thing : public WithTypedId<Thing> { Thing(int x) { } };
Chris@1764 78 * typedef TypedById<Thing, Thing::Id> ThingById;
Chris@1764 79 *
Chris@1764 80 * // application creates a new Thing
Chris@1764 81 * // ...
Chris@1764 82 * auto thing = std::make_shared<Thing>(10);
Chris@1764 83 * auto thingId = ThingById::add(thing);
Chris@1764 84 *
Chris@1764 85 * // application then passes thingId to something else, without
Chris@1764 86 * // storing the shared_ptr anywhere - the ById store manages that
Chris@1764 87 *
Chris@1764 88 * // code elsewhere now has the thingId, and needs to use the Thing
Chris@1764 89 * // ...
Chris@1764 90 * void doSomething() {
Chris@1764 91 * auto thing = ThingById::get(m_thingId);
Chris@1764 92 * if (!thing) { // the Thing has been deleted, stop acting on it
Chris@1764 93 * return; // (this may be an error or it may be unexceptional)
Chris@1764 94 * }
Chris@1764 95 * // now we have a guarantee that the thing ptr will be valid
Chris@1764 96 * // until it goes out of scope when doSomething returns
Chris@1764 97 * }
Chris@1764 98 *
Chris@1764 99 * // application wants to be rid of the Thing
Chris@1764 100 * ThingById::release(thingId);
Chris@1764 101 */
Chris@1744 102
Chris@1764 103 //!!! to do: review how often we are calling getAs<...> when we could
Chris@1764 104 // just be using get
Chris@1753 105
Chris@1742 106 struct IdAlloc {
Chris@1742 107
Chris@1742 108 // The value NO_ID (-1) is never allocated
Chris@1742 109 static const int NO_ID = -1;
Chris@1742 110
Chris@1742 111 static int getNextId();
Chris@1742 112 };
Chris@1742 113
Chris@1731 114 template <typename T>
Chris@1742 115 struct TypedId {
Chris@1735 116
Chris@1742 117 int untyped;
Chris@1742 118
Chris@1742 119 TypedId() : untyped(IdAlloc::NO_ID) {}
Chris@1729 120
Chris@1742 121 TypedId(const TypedId &) =default;
Chris@1742 122 TypedId &operator=(const TypedId &) =default;
Chris@1735 123
Chris@1742 124 bool operator==(const TypedId &other) const {
Chris@1742 125 return untyped == other.untyped;
Chris@1742 126 }
Chris@1742 127 bool operator!=(const TypedId &other) const {
Chris@1742 128 return untyped != other.untyped;
Chris@1742 129 }
Chris@1742 130 bool operator<(const TypedId &other) const {
Chris@1742 131 return untyped < other.untyped;
Chris@1742 132 }
Chris@1742 133 bool isNone() const {
Chris@1742 134 return untyped == IdAlloc::NO_ID;
Chris@1731 135 }
Chris@1731 136 };
Chris@1731 137
Chris@1731 138 template <typename T>
Chris@1739 139 std::ostream &
Chris@1742 140 operator<<(std::ostream &ostr, const TypedId<T> &id)
Chris@1739 141 {
Chris@1739 142 // For diagnostic purposes only. Do not use these IDs for
Chris@1739 143 // serialisation - see XmlExportable instead.
Chris@1739 144 if (id.isNone()) {
Chris@1739 145 return (ostr << "<none>");
Chris@1739 146 } else {
Chris@1742 147 return (ostr << "#" << id.untyped);
Chris@1739 148 }
Chris@1739 149 }
Chris@1739 150
Chris@1729 151 class WithId
Chris@1729 152 {
Chris@1729 153 public:
Chris@1729 154 WithId() :
Chris@1742 155 m_id(IdAlloc::getNextId()) {
Chris@1742 156 }
Chris@1742 157 virtual ~WithId() {
Chris@1729 158 }
Chris@1729 159
Chris@1752 160 protected:
Chris@1752 161 friend class AnyById;
Chris@1752 162
Chris@1731 163 /**
Chris@1742 164 * Return an id for this object. The id is a unique number for
Chris@1731 165 * this object among all objects that implement WithId within this
Chris@1731 166 * single run of the application.
Chris@1731 167 */
Chris@1742 168 int getUntypedId() const {
Chris@1742 169 return m_id;
Chris@1742 170 }
Chris@1742 171
Chris@1742 172 private:
Chris@1742 173 int m_id;
Chris@1742 174 };
Chris@1742 175
Chris@1742 176 template <typename T>
Chris@1742 177 class WithTypedId : virtual public WithId
Chris@1742 178 {
Chris@1742 179 public:
Chris@1742 180 typedef TypedId<T> Id;
Chris@1742 181
Chris@1742 182 WithTypedId() : WithId() { }
Chris@1742 183
Chris@1752 184 protected:
Chris@1752 185 template <typename Item, typename Id>
Chris@1752 186 friend class TypedById;
Chris@1752 187
Chris@1742 188 /**
Chris@1742 189 * Return an id for this object. The id is a unique value for this
Chris@1840 190 * object among all objects that implement WithId within this
Chris@1742 191 * single run of the application.
Chris@1742 192 */
Chris@1729 193 Id getId() const {
Chris@1731 194 Id id;
Chris@1742 195 id.untyped = getUntypedId();
Chris@1731 196 return id;
Chris@1729 197 }
Chris@1742 198 };
Chris@1742 199
Chris@1742 200 class AnyById
Chris@1742 201 {
Chris@1742 202 public:
Chris@1752 203 static int add(std::shared_ptr<WithId>);
Chris@1742 204 static void release(int);
Chris@1746 205 static std::shared_ptr<WithId> get(int);
Chris@1742 206
Chris@1742 207 template <typename Derived>
Chris@1746 208 static bool isa(int id) {
Chris@1746 209 std::shared_ptr<WithId> p = get(id);
Chris@1746 210 return bool(std::dynamic_pointer_cast<Derived>(p));
Chris@1746 211 }
Chris@1746 212
Chris@1746 213 template <typename Derived>
Chris@1742 214 static std::shared_ptr<Derived> getAs(int id) {
Chris@1742 215 std::shared_ptr<WithId> p = get(id);
Chris@1742 216 return std::dynamic_pointer_cast<Derived>(p);
Chris@1742 217 }
Chris@1729 218
Chris@1729 219 private:
Chris@1742 220 class Impl;
Chris@1742 221 static Impl &impl();
Chris@1729 222 };
Chris@1729 223
Chris@1731 224 template <typename Item, typename Id>
Chris@1742 225 class TypedById
Chris@1729 226 {
Chris@1729 227 public:
Chris@1750 228 static Id add(std::shared_ptr<Item> item) {
Chris@1752 229 Id id;
Chris@1752 230 id.untyped = AnyById::add(item);
Chris@1750 231 return id;
Chris@1729 232 }
Chris@1729 233
Chris@1742 234 static void release(Id id) {
Chris@1742 235 AnyById::release(id.untyped);
Chris@1729 236 }
Chris@1742 237 static void release(std::shared_ptr<Item> item) {
Chris@1742 238 release(item->getId());
Chris@1729 239 }
Chris@1729 240
Chris@1729 241 template <typename Derived>
Chris@1746 242 static bool isa(Id id) {
Chris@1746 243 return AnyById::isa<Derived>(id.untyped);
Chris@1746 244 }
Chris@1746 245
Chris@1746 246 template <typename Derived>
Chris@1742 247 static std::shared_ptr<Derived> getAs(Id id) {
Chris@1742 248 return AnyById::getAs<Derived>(id.untyped);
Chris@1742 249 }
Chris@1742 250
Chris@1742 251 static std::shared_ptr<Item> get(Id id) {
Chris@1742 252 return getAs<Item>(id);
Chris@1729 253 }
Chris@1752 254
Chris@1738 255 /**
Chris@1738 256 * If the Item type is an XmlExportable, return the export ID of
Chris@1739 257 * the given item ID. A call to this function will fail to compile
Chris@1739 258 * if the Item is not an XmlExportable.
Chris@1739 259 *
Chris@1739 260 * The export ID is a simple int, and is only allocated when first
Chris@1739 261 * requested, so objects that are never exported don't get one.
Chris@1738 262 */
Chris@1742 263 static int getExportId(Id id) {
Chris@1738 264 auto exportable = getAs<XmlExportable>(id);
Chris@1738 265 if (exportable) {
Chris@1738 266 return exportable->getExportId();
Chris@1738 267 } else {
Chris@1738 268 return XmlExportable::NO_ID;
Chris@1738 269 }
Chris@1738 270 }
Chris@1729 271 };
Chris@1731 272
Chris@1729 273 #endif
Chris@1729 274