Chris@1729: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1729: Chris@1729: /* Chris@1729: Sonic Visualiser Chris@1729: An audio file viewer and annotation editor. Chris@1729: Centre for Digital Music, Queen Mary, University of London. Chris@1729: Chris@1729: This program is free software; you can redistribute it and/or Chris@1729: modify it under the terms of the GNU General Public License as Chris@1729: published by the Free Software Foundation; either version 2 of the Chris@1729: License, or (at your option) any later version. See the file Chris@1729: COPYING included with this distribution for more information. Chris@1729: */ Chris@1729: Chris@1729: #ifndef SV_BY_ID_H Chris@1729: #define SV_BY_ID_H Chris@1729: Chris@1734: #include "Debug.h" Chris@1734: Chris@1729: #include Chris@1731: #include Chris@1731: #include Chris@1742: #include Chris@1729: Chris@1729: #include Chris@1731: #include Chris@1729: Chris@1738: #include "XmlExportable.h" Chris@1738: Chris@1764: /* Chris@1764: * ById - central pool of objects to be retrieved by persistent id. Chris@1764: * Chris@1764: * This is a pretty simple mechanism for obtaining safe "borrowed" Chris@1764: * references to shared objects, including across threads, based on an Chris@1764: * object ID. Chris@1764: * Chris@1764: * A class (call it C) inherits WithTypedId. This produces a type Chris@1764: * C::Id containing a numerical id. Each instance of C (or subclass Chris@1764: * thereof) has an internal id of type C::Id whose value is unique Chris@1764: * among all ids ever possessed by any instances of all classes that Chris@1764: * use this id mechanism (within a single run of the program). Chris@1764: * Chris@1764: * Then we have a static store of type TypedById. This holds Chris@1764: * a set of heap-allocated C objects (or subclass thereof) and hands Chris@1764: * out shared_ptr references to them when queried by id. The Chris@1764: * application calls add() to pass an object to the store (which takes Chris@1764: * ownership of it), and the application calls release() when it Chris@1764: * thinks it has finished with an object, to request the store to Chris@1764: * delete it. Chris@1764: * Chris@1764: * Note that an object's id can't (without shenanigans) be queried Chris@1764: * directly from that object - it is returned when the object is added Chris@1764: * to a ById store. So if you have an object id, you know that the Chris@1764: * object must have been added to a store at some point. Chris@1764: * Chris@1764: * The goal is to improve code that would previously have retained a Chris@1764: * bare pointer to a heap-allocated object that it did not own. For Chris@1764: * example, in Sonic Visualiser we have a Model hierarchy of complex Chris@1764: * mutable objects, and any given model may be referred to by many Chris@1764: * different layers, transforms (as both source and target) etc. Using Chris@1764: * bare pointers for those references means that we need everything to Chris@1764: * be notified (and act properly on the notification) if a model is Chris@1764: * about to be deleted. Using a Model::Id instead gives the code a Chris@1764: * guarantee: if the model has been deleted since you last looked at Chris@1764: * it, then the ById store will return a null shared_ptr from its Chris@1764: * get() function for that id; but if it returns a non-null Chris@1764: * shared_ptr, then the object being pointed to can't be deleted while Chris@1764: * that shared_ptr is in scope. Chris@1764: * Chris@1840: * The result is like using weak_ptr references to a shared_ptr whose Chris@1840: * ownership is within a "layer" (at "application level") rather than Chris@1840: * managed by a specific object. A human-readable id representation is Chris@1840: * also quite useful for debugging. Chris@1840: * Chris@1764: * Example: Chris@1764: * Chris@1764: * class Thing : public WithTypedId { Thing(int x) { } }; Chris@1764: * typedef TypedById ThingById; Chris@1764: * Chris@1764: * // application creates a new Thing Chris@1764: * // ... Chris@1764: * auto thing = std::make_shared(10); Chris@1764: * auto thingId = ThingById::add(thing); Chris@1764: * Chris@1764: * // application then passes thingId to something else, without Chris@1764: * // storing the shared_ptr anywhere - the ById store manages that Chris@1764: * Chris@1764: * // code elsewhere now has the thingId, and needs to use the Thing Chris@1764: * // ... Chris@1764: * void doSomething() { Chris@1764: * auto thing = ThingById::get(m_thingId); Chris@1764: * if (!thing) { // the Thing has been deleted, stop acting on it Chris@1764: * return; // (this may be an error or it may be unexceptional) Chris@1764: * } Chris@1764: * // now we have a guarantee that the thing ptr will be valid Chris@1764: * // until it goes out of scope when doSomething returns Chris@1764: * } Chris@1764: * Chris@1764: * // application wants to be rid of the Thing Chris@1764: * ThingById::release(thingId); Chris@1764: */ Chris@1744: Chris@1764: //!!! to do: review how often we are calling getAs<...> when we could Chris@1764: // just be using get Chris@1753: Chris@1742: struct IdAlloc { Chris@1742: Chris@1742: // The value NO_ID (-1) is never allocated Chris@1742: static const int NO_ID = -1; Chris@1742: Chris@1742: static int getNextId(); Chris@1742: }; Chris@1742: Chris@1731: template Chris@1742: struct TypedId { Chris@1735: Chris@1742: int untyped; Chris@1742: Chris@1742: TypedId() : untyped(IdAlloc::NO_ID) {} Chris@1729: Chris@1742: TypedId(const TypedId &) =default; Chris@1742: TypedId &operator=(const TypedId &) =default; Chris@1735: Chris@1742: bool operator==(const TypedId &other) const { Chris@1742: return untyped == other.untyped; Chris@1742: } Chris@1742: bool operator!=(const TypedId &other) const { Chris@1742: return untyped != other.untyped; Chris@1742: } Chris@1742: bool operator<(const TypedId &other) const { Chris@1742: return untyped < other.untyped; Chris@1742: } Chris@1742: bool isNone() const { Chris@1742: return untyped == IdAlloc::NO_ID; Chris@1731: } Chris@1731: }; Chris@1731: Chris@1731: template Chris@1739: std::ostream & Chris@1742: operator<<(std::ostream &ostr, const TypedId &id) Chris@1739: { Chris@1739: // For diagnostic purposes only. Do not use these IDs for Chris@1739: // serialisation - see XmlExportable instead. Chris@1739: if (id.isNone()) { Chris@1739: return (ostr << ""); Chris@1739: } else { Chris@1742: return (ostr << "#" << id.untyped); Chris@1739: } Chris@1739: } Chris@1739: Chris@1729: class WithId Chris@1729: { Chris@1729: public: Chris@1729: WithId() : Chris@1742: m_id(IdAlloc::getNextId()) { Chris@1742: } Chris@1742: virtual ~WithId() { Chris@1729: } Chris@1729: Chris@1752: protected: Chris@1752: friend class AnyById; Chris@1752: Chris@1731: /** Chris@1742: * Return an id for this object. The id is a unique number for Chris@1731: * this object among all objects that implement WithId within this Chris@1731: * single run of the application. Chris@1731: */ Chris@1742: int getUntypedId() const { Chris@1742: return m_id; Chris@1742: } Chris@1742: Chris@1742: private: Chris@1742: int m_id; Chris@1742: }; Chris@1742: Chris@1742: template Chris@1742: class WithTypedId : virtual public WithId Chris@1742: { Chris@1742: public: Chris@1742: typedef TypedId Id; Chris@1742: Chris@1742: WithTypedId() : WithId() { } Chris@1742: Chris@1752: protected: Chris@1752: template Chris@1752: friend class TypedById; Chris@1752: Chris@1742: /** Chris@1742: * Return an id for this object. The id is a unique value for this Chris@1840: * object among all objects that implement WithId within this Chris@1742: * single run of the application. Chris@1742: */ Chris@1729: Id getId() const { Chris@1731: Id id; Chris@1742: id.untyped = getUntypedId(); Chris@1731: return id; Chris@1729: } Chris@1742: }; Chris@1742: Chris@1742: class AnyById Chris@1742: { Chris@1742: public: Chris@1752: static int add(std::shared_ptr); Chris@1742: static void release(int); Chris@1746: static std::shared_ptr get(int); Chris@1742: Chris@1742: template Chris@1746: static bool isa(int id) { Chris@1746: std::shared_ptr p = get(id); Chris@1746: return bool(std::dynamic_pointer_cast(p)); Chris@1746: } Chris@1746: Chris@1746: template Chris@1742: static std::shared_ptr getAs(int id) { Chris@1742: std::shared_ptr p = get(id); Chris@1742: return std::dynamic_pointer_cast(p); Chris@1742: } Chris@1729: Chris@1729: private: Chris@1742: class Impl; Chris@1742: static Impl &impl(); Chris@1729: }; Chris@1729: Chris@1731: template Chris@1742: class TypedById Chris@1729: { Chris@1729: public: Chris@1750: static Id add(std::shared_ptr item) { Chris@1752: Id id; Chris@1752: id.untyped = AnyById::add(item); Chris@1750: return id; Chris@1729: } Chris@1729: Chris@1742: static void release(Id id) { Chris@1742: AnyById::release(id.untyped); Chris@1729: } Chris@1742: static void release(std::shared_ptr item) { Chris@1742: release(item->getId()); Chris@1729: } Chris@1729: Chris@1729: template Chris@1746: static bool isa(Id id) { Chris@1746: return AnyById::isa(id.untyped); Chris@1746: } Chris@1746: Chris@1746: template Chris@1742: static std::shared_ptr getAs(Id id) { Chris@1742: return AnyById::getAs(id.untyped); Chris@1742: } Chris@1742: Chris@1742: static std::shared_ptr get(Id id) { Chris@1742: return getAs(id); Chris@1729: } Chris@1752: Chris@1738: /** Chris@1738: * If the Item type is an XmlExportable, return the export ID of Chris@1739: * the given item ID. A call to this function will fail to compile Chris@1739: * if the Item is not an XmlExportable. Chris@1739: * Chris@1739: * The export ID is a simple int, and is only allocated when first Chris@1739: * requested, so objects that are never exported don't get one. Chris@1738: */ Chris@1742: static int getExportId(Id id) { Chris@1738: auto exportable = getAs(id); Chris@1738: if (exportable) { Chris@1738: return exportable->getExportId(); Chris@1738: } else { Chris@1738: return XmlExportable::NO_ID; Chris@1738: } Chris@1738: } Chris@1729: }; Chris@1731: Chris@1729: #endif Chris@1729: