# HG changeset patch # User Chris Cannam # Date 1561739790 -3600 # Node ID 52705a328b34e7b17d4ac8c5bf460da4d305f187 # Parent 9d82b164f2644ed5d0a2e88b5ab6bf4355b2b468 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 diff -r 9d82b164f264 -r 52705a328b34 base/ById.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ById.cpp Fri Jun 28 17:36:30 2019 +0100 @@ -0,0 +1,122 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ById.h" + +#include +#include + +int IdAlloc::getNextId() +{ + static int nextId = 0; + static QMutex mutex; + QMutexLocker locker(&mutex); + int i = nextId; + if (nextId == INT_MAX) { + nextId = INT_MIN; + } else { + ++nextId; + if (nextId == 0 || nextId == NO_ID) { + throw std::runtime_error("Internal ID limit exceeded!"); + } + } + return i; +} + +class AnyById::Impl +{ +public: + ~Impl() { + QMutexLocker locker(&m_mutex); + bool empty = true; + for (const auto &p: m_items) { + if (p.second && p.second.use_count() > 0) { + empty = false; + break; + } + } + if (!empty) { + SVCERR << "WARNING: ById map is not empty at close; some items have not been released" << endl; + SVCERR << " Unreleased items are:" << endl; + for (const auto &p: m_items) { + if (p.second && p.second.use_count() > 0) { + SVCERR << " - id #" << p.first + << ": type " << typeid(*p.second.get()).name() + << ", use count " << p.second.use_count() << endl; + } + } + } + } + + void add(int id, std::shared_ptr item) { + QMutexLocker locker(&m_mutex); + if (m_items.find(id) != m_items.end()) { + SVCERR << "ById::add: item with id " << id + << " is already recorded (existing item type is " + << typeid(*m_items.find(id)->second.get()).name() + << ", proposed is " + << typeid(*item.get()).name() << ")" << endl; + throw std::logic_error("item id is already recorded in add"); + } + m_items[id] = item; + } + + void release(int id) { + QMutexLocker locker(&m_mutex); + if (m_items.find(id) == m_items.end()) { + SVCERR << "ById::release: unknown item id " << id << endl; + throw std::logic_error("unknown item id in release"); + } + m_items.erase(id); + } + + std::shared_ptr get(int id) const { + QMutexLocker locker(&m_mutex); + const auto &itr = m_items.find(id); + if (itr != m_items.end()) { + return itr->second; + } else { + return {}; + } + } + +private: + mutable QMutex m_mutex; + std::unordered_map> m_items; +}; + +void +AnyById::add(int id, std::shared_ptr item) +{ + impl().add(id, item); +} + +void +AnyById::release(int id) +{ + impl().release(id); +} + +std::shared_ptr +AnyById::get(int id) +{ + return impl().get(id); +} + +AnyById::Impl & +AnyById::impl() +{ + static Impl impl; + return impl; +} diff -r 9d82b164f264 -r 52705a328b34 base/ById.h --- a/base/ById.h Thu Jun 27 13:08:10 2019 +0100 +++ b/base/ById.h Fri Jun 28 17:36:30 2019 +0100 @@ -18,145 +18,147 @@ #include "Debug.h" #include -#include -#include #include #include +#include #include #include #include "XmlExportable.h" +struct IdAlloc { + + // The value NO_ID (-1) is never allocated + static const int NO_ID = -1; + + static int getNextId(); +}; + template -struct SvId { +struct TypedId { - int id; + int untyped; + + TypedId() : untyped(IdAlloc::NO_ID) {} - enum { - // The value NO_ID (-1) is never allocated by WithId - NO_ID = -1 - }; - - SvId() : id(NO_ID) {} + TypedId(const TypedId &) =default; + TypedId &operator=(const TypedId &) =default; - SvId(const SvId &) =default; - SvId &operator=(const SvId &) =default; - - bool operator==(const SvId &other) const { return id == other.id; } - bool operator<(const SvId &other) const { return id < other.id; } - - bool isNone() const { return id == NO_ID; } - - QString toString() const { - return QString("%1").arg(id); + bool operator==(const TypedId &other) const { + return untyped == other.untyped; + } + bool operator!=(const TypedId &other) const { + return untyped != other.untyped; + } + bool operator<(const TypedId &other) const { + return untyped < other.untyped; + } + bool isNone() const { + return untyped == IdAlloc::NO_ID; } }; template std::ostream & -operator<<(std::ostream &ostr, const SvId &id) +operator<<(std::ostream &ostr, const TypedId &id) { // For diagnostic purposes only. Do not use these IDs for // serialisation - see XmlExportable instead. if (id.isNone()) { return (ostr << ""); } else { - return (ostr << "#" << id.id); + return (ostr << "#" << id.untyped); } } -template class WithId { public: - typedef SvId Id; - WithId() : - m_id(getNextId()) { + m_id(IdAlloc::getNextId()) { + } + virtual ~WithId() { } /** - * Return an id for this object. The id is a unique identifier for + * Return an id for this object. The id is a unique number for * this object among all objects that implement WithId within this * single run of the application. */ + int getUntypedId() const { + return m_id; + } + +private: + int m_id; +}; + +template +class WithTypedId : virtual public WithId +{ +public: + typedef TypedId Id; + + WithTypedId() : WithId() { } + + /** + * Return an id for this object. The id is a unique value for this + * object among all objects that implement WithTypedId within this + * single run of the application. + */ Id getId() const { Id id; - id.id = m_id; + id.untyped = getUntypedId(); return id; } +}; + +class AnyById +{ +public: + static void add(int, std::shared_ptr); + static void release(int); + static std::shared_ptr get(int); + + template + static std::shared_ptr getAs(int id) { + std::shared_ptr p = get(id); + return std::dynamic_pointer_cast(p); + } private: - int m_id; - - static int getNextId() { - static int nextId = 0; - static QMutex mutex; - QMutexLocker locker(&mutex); - int i = nextId; - if (nextId == INT_MAX) { - nextId = INT_MIN; - } else { - ++nextId; - if (nextId == 0 || nextId == Id::NO_ID) { - throw std::runtime_error("Internal ID limit exceeded!"); - } - } - return i; - } + class Impl; + static Impl &impl(); }; template -class ById +class TypedById { public: - ~ById() { - QMutexLocker locker(&m_mutex); - for (const auto &p: m_items) { - if (p.second && p.second.use_count() > 0) { - SVCERR << "WARNING: ById map destroyed with use count of " - << p.second.use_count() << " for item with type " - << typeid(*p.second.get()).name() - << " and id " << p.first.id << endl; - } - } - } - - void add(std::shared_ptr item) { - QMutexLocker locker(&m_mutex); + static void add(std::shared_ptr item) { auto id = item->getId(); if (id.isNone()) { throw std::logic_error("item id should never be None"); } - if (m_items.find(id) != m_items.end()) { - SVCERR << "WARNING: ById::add: item with id " << id - << " is already recorded, replacing it (item type is " - << typeid(*item.get()).name() << ")" << endl; - } - m_items[id] = item; + AnyById::add(id.untyped, item); } - void - release(Id id) { - QMutexLocker locker(&m_mutex); - m_items.erase(id); + static void release(Id id) { + AnyById::release(id.untyped); } - - std::shared_ptr get(Id id) const { - if (id.isNone()) return {}; // this id is never issued: avoid locking - QMutexLocker locker(&m_mutex); - const auto &itr = m_items.find(id); - if (itr != m_items.end()) { - return itr->second; - } else { - return {}; - } + static void release(std::shared_ptr item) { + release(item->getId()); } template - std::shared_ptr getAs(Id id) const { - return std::dynamic_pointer_cast(get(id)); + static std::shared_ptr getAs(Id id) { + if (id.isNone()) return {}; // this id is never issued: avoid locking + return AnyById::getAs(id.untyped); + } + + static std::shared_ptr get(Id id) { + return getAs(id); } /** @@ -167,7 +169,7 @@ * The export ID is a simple int, and is only allocated when first * requested, so objects that are never exported don't get one. */ - int getExportId(Id id) const { + static int getExportId(Id id) { auto exportable = getAs(id); if (exportable) { return exportable->getExportId(); @@ -175,44 +177,6 @@ return XmlExportable::NO_ID; } } - -private: - mutable QMutex m_mutex; - std::map> m_items; -}; - -template -class StaticById -{ -public: - static void add(std::shared_ptr imagined) { - byId().add(imagined); - } - - static void release(Id id) { - byId().release(id); - } - - static std::shared_ptr get(Id id) { - return byId().get(id); - } - - template - static - std::shared_ptr getAs(Id id) { - return std::dynamic_pointer_cast(get(id)); - } - - static int getExportId(Id id) { - return byId().getExportId(id); - } - -private: - static - ById &byId() { - static ById b; - return b; - } }; #endif diff -r 9d82b164f264 -r 52705a328b34 base/PlayParameterRepository.cpp --- a/base/PlayParameterRepository.cpp Thu Jun 27 13:08:10 2019 +0100 +++ b/base/PlayParameterRepository.cpp Fri Jun 28 17:36:30 2019 +0100 @@ -17,6 +17,8 @@ #include "PlayParameters.h" #include "Playable.h" +#include "ById.h" + #include PlayParameterRepository * @@ -33,19 +35,25 @@ } void -PlayParameterRepository::addPlayable(const Playable *playable) +PlayParameterRepository::addPlayable(int playableId) { // cerr << "PlayParameterRepository:addPlayable playable = " << playable << endl; - if (!getPlayParameters(playable)) { + if (!getPlayParameters(playableId)) { + auto playable = AnyById::getAs(playableId); + if (!playable) { + SVCERR << "ERROR: id passed to PlayParameterRepository::addPlayable is not that of a Playable" << endl; + return; + } + // Give all playables the same type of play parameters for the // moment // cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl; PlayParameters *params = new PlayParameters; - m_playParameters[playable] = params; + m_playParameters[playableId] = params; params->setPlayClipId (playable->getDefaultPlayClipId()); @@ -65,18 +73,18 @@ } void -PlayParameterRepository::removePlayable(const Playable *playable) +PlayParameterRepository::removePlayable(int playableId) { - if (m_playParameters.find(playable) == m_playParameters.end()) { - cerr << "WARNING: PlayParameterRepository::removePlayable: unknown playable " << playable << endl; + if (m_playParameters.find(playableId) == m_playParameters.end()) { + SVCERR << "WARNING: PlayParameterRepository::removePlayable: unknown playable " << playableId << endl; return; } - delete m_playParameters[playable]; - m_playParameters.erase(playable); + delete m_playParameters[playableId]; + m_playParameters.erase(playableId); } void -PlayParameterRepository::copyParameters(const Playable *from, const Playable *to) +PlayParameterRepository::copyParameters(int from, int to) { if (!getPlayParameters(from)) { cerr << "ERROR: PlayParameterRepository::copyParameters: source playable unknown" << endl; @@ -90,10 +98,12 @@ } PlayParameters * -PlayParameterRepository::getPlayParameters(const Playable *playable) +PlayParameterRepository::getPlayParameters(int playableId) { - if (m_playParameters.find(playable) == m_playParameters.end()) return nullptr; - return m_playParameters.find(playable)->second; + if (m_playParameters.find(playableId) == m_playParameters.end()) { + return nullptr; + } + return m_playParameters.find(playableId)->second; } void diff -r 9d82b164f264 -r 52705a328b34 base/PlayParameterRepository.h --- a/base/PlayParameterRepository.h Thu Jun 27 13:08:10 2019 +0100 +++ b/base/PlayParameterRepository.h Fri Jun 28 17:36:30 2019 +0100 @@ -35,11 +35,37 @@ virtual ~PlayParameterRepository(); - void addPlayable(const Playable *playable); - void removePlayable(const Playable *playable); - void copyParameters(const Playable *from, const Playable *to); + /** + * Register a playable. + * + * The id must be of an object that is registered with the ById + * store and that can be dynamic_cast to Playable. + */ + void addPlayable(int playableId); - PlayParameters *getPlayParameters(const Playable *playable); + /** + * Unregister a playable. + * + * The id must be of an object that is registered with the ById + * store and that can be dynamic_cast to Playable. + */ + void removePlayable(int playableId); + + /** + * Copy the play parameters from one playable to another. + * + * The ids must be of objects that are registered with the ById + * store and that can be dynamic_cast to Playable. + */ + void copyParameters(int fromId, int toId); + + /** + * Retrieve the play parameters for a playable. + * + * The id must be of an object that is registered with the ById + * store and that can be dynamic_cast to Playable. + */ + PlayParameters *getPlayParameters(int playableId); void clear(); @@ -64,14 +90,14 @@ signals: void playParametersChanged(PlayParameters *); - void playClipIdChanged(const Playable *, QString); + void playClipIdChanged(int playableId, QString); protected slots: void playParametersChanged(); void playClipIdChanged(QString); protected: - typedef std::map PlayableParameterMap; + typedef std::map PlayableParameterMap; PlayableParameterMap m_playParameters; static PlayParameterRepository *m_instance; diff -r 9d82b164f264 -r 52705a328b34 base/PlayParameters.h --- a/base/PlayParameters.h Thu Jun 27 13:08:10 2019 +0100 +++ b/base/PlayParameters.h Fri Jun 28 17:36:30 2019 +0100 @@ -37,8 +37,8 @@ virtual void copyFrom(const PlayParameters *); void toXml(QTextStream &stream, - QString indent = "", - QString extraAttributes = "") const override; + QString indent = "", + QString extraAttributes = "") const override; public slots: virtual void setPlayMuted(bool muted); diff -r 9d82b164f264 -r 52705a328b34 base/test/TestById.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestById.h Fri Jun 28 17:36:30 2019 +0100 @@ -0,0 +1,191 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "../ById.h" + +#include +#include + +#include + +using namespace std; + +struct WithoutId {}; + +struct A : public WithTypedId {}; +struct B1 : public A {}; +struct B2 : public A {}; + +struct M {}; + +typedef TypedById AById; + +struct X : virtual public WithId {}; +struct Y : public X, public B2, public M {}; + +class TestById : public QObject +{ + Q_OBJECT + +private slots: + void ids() { + // Verify that ids are unique across all classes, not just + // within a class. These must be the first two WithId objects + // allocated in the first test in the suite, otherwise they + // could be different even if they were allocated from + // separate pools. + A a; + X x; + if (a.getId().untyped == x.getUntypedId()) { + cerr << "ERROR: a and x have the same id: " << a.getId() << endl; + } + QVERIFY(a.getId().untyped != x.getUntypedId()); + + A aa; + QVERIFY(aa.getId().untyped != a.getId().untyped); + QVERIFY(aa.getId().untyped != x.getUntypedId()); + + // Check the actual ids that have been allocated. This is + // supposed to be a hidden implementation detail, but we want + // to make sure the test itself hasn't become broken in terms + // of allocation order (see comment above) + QCOMPARE(a.getId().untyped, 0); + QCOMPARE(x.getUntypedId(), 1); + QCOMPARE(aa.getId().untyped, 2); + + QVERIFY(!a.getId().isNone()); + QVERIFY(A::Id().isNone()); + } + + // NB each test must release all the items it adds to the ById store + + void anyEmpty() { + auto p = AnyById::get(0); + QVERIFY(!p); + } + + void anySimple() { + auto a = std::make_shared(); + AnyById::add(a->getId().untyped, a); + + auto aa = AnyById::getAs(a->getId().untyped); + QVERIFY(!!aa); + QCOMPARE(aa->getId(), a->getId()); + QCOMPARE(aa.get(), a.get()); // same object, not just same id! + AnyById::release(a->getId().untyped); + } + + void typedEmpty() { + auto p = AById::get({}); + QVERIFY(!p); + } + + void typedSimple() { + auto a = std::make_shared(); + AById::add(a); + + auto aa = AById::get(a->getId()); + QVERIFY(!!aa); + QCOMPARE(aa->getId(), a->getId()); + QCOMPARE(aa.get(), a.get()); // same object, not just same id! + AById::release(a); + } + + void typedRelease() { + auto a = std::make_shared(); + AById::add(a); + + auto aa = AById::get(a->getId()); + QVERIFY(!!aa); + AById::release(a); + + aa = AById::get(a->getId()); + QVERIFY(!aa); + } + + void typedDowncast() { + auto a = std::make_shared(); + auto b1 = std::make_shared(); + AById::add(a); + AById::add(b1); + + auto bb1 = AById::getAs(a->getId()); + QVERIFY(!bb1); + + bb1 = AById::getAs(b1->getId()); + QVERIFY(!!bb1); + QCOMPARE(bb1->getId(), b1->getId()); + + auto bb2 = AById::getAs(b1->getId()); + QVERIFY(!bb2); + + AById::release(a); + AById::release(b1); + } + + void typedCrosscast() { + auto y = std::make_shared(); + AById::add(y); + + auto yy = AById::getAs(y->getId()); + QVERIFY(!!yy); + QCOMPARE(yy->getId(), y->getId()); + + yy = AnyById::getAs(y->getId().untyped); + QVERIFY(!!yy); + QCOMPARE(yy->getId(), y->getId()); + + auto xx = AById::getAs(y->getId()); + QVERIFY(!!xx); + QCOMPARE(xx->getUntypedId(), y->getId().untyped); + QCOMPARE(xx.get(), yy.get()); + + xx = AnyById::getAs(y->getId().untyped); + QVERIFY(!!xx); + QCOMPARE(xx->getUntypedId(), y->getId().untyped); + QCOMPARE(xx.get(), yy.get()); + + auto mm = AnyById::getAs(y->getId().untyped); + QVERIFY(!!mm); + QCOMPARE(mm.get(), yy.get()); + + AById::release(y); + } + + void duplicateAdd() { + auto a = std::make_shared(); + AById::add(a); + try { + AById::add(a); + cerr << "Failed to catch expected exception in duplicateAdd" << endl; + QVERIFY(false); + } catch (const std::logic_error &) { + } + AById::release(a); + } + + void unknownRelease() { + auto a = std::make_shared(); + auto b1 = std::make_shared(); + AById::add(a); + try { + AById::release(b1); + cerr << "Failed to catch expected exception in unknownRelease" << endl; + QVERIFY(false); + } catch (const std::logic_error &) { + } + AById::release(a); + } +}; + diff -r 9d82b164f264 -r 52705a328b34 base/test/files.pri --- a/base/test/files.pri Thu Jun 27 13:08:10 2019 +0100 +++ b/base/test/files.pri Fri Jun 28 17:36:30 2019 +0100 @@ -1,4 +1,5 @@ TEST_HEADERS = \ + TestById.h \ TestColumnOp.h \ TestLogRange.h \ TestMovingMedian.h \ diff -r 9d82b164f264 -r 52705a328b34 base/test/svcore-base-test.cpp --- a/base/test/svcore-base-test.cpp Thu Jun 27 13:08:10 2019 +0100 +++ b/base/test/svcore-base-test.cpp Fri Jun 28 17:36:30 2019 +0100 @@ -20,6 +20,7 @@ #include "TestVampRealTime.h" #include "TestColumnOp.h" #include "TestMovingMedian.h" +#include "TestById.h" #include "TestEventSeries.h" #include "StressEventSeries.h" @@ -91,13 +92,19 @@ if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } -/* + { + TestById t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + +#ifdef NOT_DEFINED { StressEventSeries t; if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } -*/ +#endif if (bad > 0) { SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl; diff -r 9d82b164f264 -r 52705a328b34 data/model/DenseTimeValueModel.cpp --- a/data/model/DenseTimeValueModel.cpp Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/DenseTimeValueModel.cpp Fri Jun 28 17:36:30 2019 +0100 @@ -20,12 +20,12 @@ DenseTimeValueModel::DenseTimeValueModel() { - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable(getId().untyped); } DenseTimeValueModel::~DenseTimeValueModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable(getId().untyped); } QString diff -r 9d82b164f264 -r 52705a328b34 data/model/EventCommands.h --- a/data/model/EventCommands.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/EventCommands.h Fri Jun 28 17:36:30 2019 +0100 @@ -30,38 +30,34 @@ virtual void remove(Event e) = 0; }; -template class WithEditable { - typedef typename Base::Id Id; - Id m_id; - protected: - WithEditable(Id id) : m_id(id) { } + WithEditable(int editableId) : m_editableId(editableId) { } std::shared_ptr getEditable() { - auto base = StaticById::get(m_id); - if (!base) return {}; // acceptable - item has expired - auto editable = std::dynamic_pointer_cast(base); + auto editable = AnyById::getAs(m_editableId); if (!editable) { SVCERR << "WARNING: Id passed to EventEditable command is not that of an EventEditable" << endl; } return editable; } + +private: + int m_editableId; }; /** * Command to add an event to an editable containing events, with - * undo. The template parameter must be a type that can be - * dynamic_cast to EventEditable and that has a ById store. + * undo. The id must be that of a type that can be retrieved from the + * AnyById store and dynamic_cast to EventEditable. */ -template class AddEventCommand : public Command, - public WithEditable + public WithEditable { public: - AddEventCommand(typename Base::Id editable, const Event &e, QString name) : - WithEditable(editable), m_event(e), m_name(name) { } + AddEventCommand(int editableId, const Event &e, QString name) : + WithEditable(editableId), m_event(e), m_name(name) { } QString getName() const override { return m_name; } Event getEvent() const { return m_event; } @@ -78,21 +74,19 @@ private: Event m_event; QString m_name; - using WithEditable::getEditable; }; /** * Command to remove an event from an editable containing events, with - * undo. The template parameter must be a type that implements - * EventBase and that has a ById store. + * undo. The id must be that of a type that can be retrieved from the + * AnyById store and dynamic_cast to EventEditable. */ -template class RemoveEventCommand : public Command, - public WithEditable + public WithEditable { public: - RemoveEventCommand(typename Base::Id editable, const Event &e, QString name) : - WithEditable(editable), m_event(e), m_name(name) { } + RemoveEventCommand(int editableId, const Event &e, QString name) : + WithEditable(editableId), m_event(e), m_name(name) { } QString getName() const override { return m_name; } Event getEvent() const { return m_event; } @@ -109,32 +103,26 @@ private: Event m_event; QString m_name; - using WithEditable::getEditable; }; /** * Command to add or remove a series of events to or from an editable, * with undo. Creates and immediately executes a sub-command for each * add/remove requested. Consecutive add/remove pairs for the same - * point are collapsed. The template parameter must be a type that - * implements EventBase and that has a ById store. + * point are collapsed. The id must be that of a type that can be + * retrieved from the AnyById store and dynamic_cast to EventEditable. */ -template class ChangeEventsCommand : public MacroCommand { - typedef typename Base::Id Id; - public: - ChangeEventsCommand(Id editable, QString name) : - MacroCommand(name), m_editable(editable) { } + ChangeEventsCommand(int editableId, QString name) : + MacroCommand(name), m_editableId(editableId) { } void add(Event e) { - addCommand(new AddEventCommand(m_editable, e, getName()), - true); + addCommand(new AddEventCommand(m_editableId, e, getName()), true); } void remove(Event e) { - addCommand(new RemoveEventCommand(m_editable, e, getName()), - true); + addCommand(new RemoveEventCommand(m_editableId, e, getName()), true); } /** @@ -166,10 +154,10 @@ return; } - RemoveEventCommand *r = - dynamic_cast *>(command); - AddEventCommand *a = - dynamic_cast *>(*m_commands.rbegin()); + RemoveEventCommand *r = + dynamic_cast(command); + AddEventCommand *a = + dynamic_cast(*m_commands.rbegin()); if (r && a) { if (a->getEvent() == r->getEvent()) { deleteCommand(a); @@ -180,7 +168,7 @@ MacroCommand::addCommand(command); } - Id m_editable; + int m_editableId; }; #endif diff -r 9d82b164f264 -r 52705a328b34 data/model/ImageModel.h --- a/data/model/ImageModel.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/ImageModel.h Fri Jun 28 17:36:30 2019 +0100 @@ -231,7 +231,7 @@ case 3: e1 = e0.withLabel(value.toString()); break; } - auto command = new ChangeEventsCommand(getId(), tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 9d82b164f264 -r 52705a328b34 data/model/Labeller.h --- a/data/model/Labeller.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/Labeller.h Fri Jun 28 17:36:30 2019 +0100 @@ -225,17 +225,16 @@ * Relabel all events in the given event vector that lie within * the given multi-selection, according to the labelling * properties of this labeller. Return a command that has been - * executed but not yet added to the history. The template - * parameter must be a type that can be dynamic_cast to - * EventEditable and that has a ById store. + * executed but not yet added to the history. The id must be that + * of a type that can be retrieved from the AnyById store and + * dynamic_cast to EventEditable. */ - template - Command *labelAll(typename EditableBase::Id editable, + Command *labelAll(int editableId, MultiSelection *ms, const EventVector &allEvents) { - auto command = new ChangeEventsCommand - (editable, tr("Label Points")); + auto command = new ChangeEventsCommand + (editableId, tr("Label Points")); Event prev; bool havePrev = false; @@ -273,18 +272,17 @@ * that event lies within the given multi-selection, add n-1 new * events at equally spaced intervals between it and the following * event. Return a command that has been executed but not yet - * added to the history. The template parameter must be a type - * that can be dynamic_cast to EventEditable and that has a ById - * store. + * added to the history. The id must be that of a type that can + * be retrieved from the AnyById store and dynamic_cast to + * EventEditable. */ - template - Command *subdivide(typename EditableBase::Id editable, + Command *subdivide(int editableId, MultiSelection *ms, const EventVector &allEvents, int n) { - auto command = new ChangeEventsCommand - (editable, tr("Subdivide Points")); + auto command = new ChangeEventsCommand + (editableId, tr("Subdivide Points")); for (auto i = allEvents.begin(); i != allEvents.end(); ++i) { @@ -325,18 +323,17 @@ * multi-selection, and a number n, remove all but every nth event * from the vector within the extents of the multi-selection. * Return a command that has been executed but not yet added to - * the history. The template parameter must be a type - * that can be dynamic_cast to EventEditable and that has a ById - * store. + * the history. The id must be that of a type that can be + * retrieved from the AnyById store and dynamic_cast to + * EventEditable. */ - template - Command *winnow(typename EditableBase::Id editable, + Command *winnow(int editableId, MultiSelection *ms, const EventVector &allEvents, int n) { - auto command = new ChangeEventsCommand - (editable, tr("Winnow Points")); + auto command = new ChangeEventsCommand + (editableId, tr("Winnow Points")); int counter = 0; diff -r 9d82b164f264 -r 52705a328b34 data/model/Model.h --- a/data/model/Model.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/Model.h Fri Jun 28 17:36:30 2019 +0100 @@ -33,7 +33,7 @@ * of data on a time scale based on an audio frame rate. */ class Model : public QObject, - public WithId, + public WithTypedId, public XmlExportable, public Playable { @@ -363,6 +363,6 @@ }; typedef Model::Id ModelId; -typedef StaticById ModelById; +typedef TypedById ModelById; #endif diff -r 9d82b164f264 -r 52705a328b34 data/model/NoteModel.h --- a/data/model/NoteModel.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/NoteModel.h Fri Jun 28 17:36:30 2019 +0100 @@ -66,7 +66,7 @@ m_valueMinimum = 33.f; m_valueMaximum = 88.f; } - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable(getId().untyped); } NoteModel(sv_samplerate_t sampleRate, int resolution, @@ -87,11 +87,11 @@ DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), m_completion(100) { - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable(getId().untyped); } virtual ~NoteModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable(getId().untyped); } QString getTypeName() const override { return tr("Note"); } @@ -308,7 +308,7 @@ case 5: e1 = e0.withLabel(value.toString()); break; } - auto command = new ChangeEventsCommand(getId(), tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 9d82b164f264 -r 52705a328b34 data/model/RegionModel.h --- a/data/model/RegionModel.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/RegionModel.h Fri Jun 28 17:36:30 2019 +0100 @@ -291,7 +291,7 @@ case 4: e1 = e0.withLabel(value.toString()); break; } - auto command = new ChangeEventsCommand(getId(), tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 9d82b164f264 -r 52705a328b34 data/model/SparseOneDimensionalModel.h --- a/data/model/SparseOneDimensionalModel.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/SparseOneDimensionalModel.h Fri Jun 28 17:36:30 2019 +0100 @@ -53,11 +53,11 @@ DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), m_completion(100) { - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable(getId().untyped); } virtual ~SparseOneDimensionalModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable(getId().untyped); } QString getTypeName() const override { return tr("Sparse 1-D"); } @@ -239,7 +239,7 @@ case 2: e1 = e0.withLabel(value.toString()); break; } - auto command = new ChangeEventsCommand(getId(), tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 9d82b164f264 -r 52705a328b34 data/model/SparseTimeValueModel.h --- a/data/model/SparseTimeValueModel.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/SparseTimeValueModel.h Fri Jun 28 17:36:30 2019 +0100 @@ -54,7 +54,7 @@ m_completion(100) { // Model is playable, but may not sound (if units not Hz or // range unsuitable) - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable(getId().untyped); } SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, @@ -73,11 +73,11 @@ m_completion(100) { // Model is playable, but may not sound (if units not Hz or // range unsuitable) - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable(getId().untyped); } virtual ~SparseTimeValueModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable(getId().untyped); } QString getTypeName() const override { return tr("Sparse Time-Value"); } @@ -289,7 +289,7 @@ case 3: e1 = e0.withLabel(value.toString()); break; } - auto command = new ChangeEventsCommand(getId(), tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 9d82b164f264 -r 52705a328b34 data/model/TextModel.h --- a/data/model/TextModel.h Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/TextModel.h Fri Jun 28 17:36:30 2019 +0100 @@ -226,7 +226,7 @@ case 3: e1 = e0.withLabel(value.toString()); break; } - auto command = new ChangeEventsCommand(getId(), tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 9d82b164f264 -r 52705a328b34 data/model/WritableWaveFileModel.cpp --- a/data/model/WritableWaveFileModel.cpp Thu Jun 27 13:08:10 2019 +0100 +++ b/data/model/WritableWaveFileModel.cpp Fri Jun 28 17:36:30 2019 +0100 @@ -97,7 +97,7 @@ // model ID should be ok QDir dir(TempDirectory::getInstance()->getPath()); path = dir.filePath(QString("written_%1.wav") - .arg(getId().toString())); + .arg(getId().untyped)); } catch (const DirectoryCreationFailed &f) { SVCERR << "WritableWaveFileModel: Failed to create temporary directory" << endl; return; @@ -128,7 +128,7 @@ // the filename only needs to be unique within that QDir dir(TempDirectory::getInstance()->getPath()); m_temporaryPath = dir.filePath(QString("prenorm_%1.wav") - .arg(getId().toString())); + .arg(getId().untyped)); m_temporaryWriter = new WavFileWriter (m_temporaryPath, m_sampleRate, m_channels, diff -r 9d82b164f264 -r 52705a328b34 files.pri --- a/files.pri Thu Jun 27 13:08:10 2019 +0100 +++ b/files.pri Fri Jun 28 17:36:30 2019 +0100 @@ -150,6 +150,7 @@ SVCORE_SOURCES = \ base/AudioLevel.cpp \ + base/ById.cpp \ base/Clipboard.cpp \ base/ColumnOp.cpp \ base/Command.cpp \