diff --git a/locale/POTFILES.in b/locale/POTFILES.in index 89a29e878..2c7b5ab6b 100644 --- a/locale/POTFILES.in +++ b/locale/POTFILES.in @@ -61,6 +61,8 @@ src/Clipboard.h src/CrashReport.cpp src/CrashReport.h src/ClassicThemeAsCeeCode.h +src/ClientData.h +src/ClientDataHelpers.h src/CrossFade.cpp src/CrossFade.h src/DarkThemeAsCeeCode.h diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index cc24b65ed..c3452a5f5 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -3172,6 +3172,7 @@ 5E08E013217E5F66003C6C99 /* LabelMenus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LabelMenus.cpp; path = menus/LabelMenus.cpp; sourceTree = ""; }; 5E0A1CDB20E95FF7001AAF8D /* CellularPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CellularPanel.cpp; sourceTree = ""; }; 5E0A1CDC20E95FF7001AAF8D /* CellularPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CellularPanel.h; sourceTree = ""; }; + 5E0D233E21B468BF0057D7C3 /* ClientData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClientData.h; sourceTree = ""; }; 5E10D9041EC8F81300B3AC57 /* PlayableTrackButtonHandles.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlayableTrackButtonHandles.cpp; sourceTree = ""; }; 5E10D9051EC8F81300B3AC57 /* PlayableTrackButtonHandles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayableTrackButtonHandles.h; sourceTree = ""; }; 5E1512381DB000C000702E29 /* HitTestResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HitTestResult.h; sourceTree = ""; }; @@ -3232,6 +3233,7 @@ 5E667A641F0BEE8C00C942A5 /* NoteTrackVZoomHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NoteTrackVZoomHandle.h; sourceTree = ""; }; 5E667A671F0D723A00C942A5 /* TrackPanelResizerCell.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrackPanelResizerCell.cpp; sourceTree = ""; }; 5E667A681F0D723A00C942A5 /* TrackPanelResizerCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelResizerCell.h; sourceTree = ""; }; + 5E6E060321BD98E700130DE0 /* ClientDataHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClientDataHelpers.h; sourceTree = ""; }; 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PopupMenuTable.cpp; sourceTree = ""; }; 5E73963A1DAFD82D00BA0A4D /* PopupMenuTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupMenuTable.h; sourceTree = ""; }; 5E73963C1DAFD86000BA0A4D /* ZoomHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZoomHandle.cpp; sourceTree = ""; }; @@ -4414,6 +4416,8 @@ 1790AFE909883BFD008A330A /* BlockFile.h */, 5E0A1CDC20E95FF7001AAF8D /* CellularPanel.h */, 5E60AC79214C31B100A82791 /* ClassicThemeAsCeeCode.h */, + 5E0D233E21B468BF0057D7C3 /* ClientData.h */, + 5E6E060321BD98E700130DE0 /* ClientDataHelpers.h */, 5EFEAD9D22723E390077DFF6 /* Clipboard.h */, 1790AFF009883BFD008A330A /* configtemplate.h */, 5EFEADA222733DD30077DFF6 /* CrashReport.h */, diff --git a/src/ClientData.h b/src/ClientData.h new file mode 100644 index 000000000..b001c2f91 --- /dev/null +++ b/src/ClientData.h @@ -0,0 +1,465 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +ClientData.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_CLIENT_DATA__ +#define __AUDACITY_CLIENT_DATA__ + +#include "ClientDataHelpers.h" + +#include +#include +#include +#include +#include "InconsistencyException.h" + +namespace ClientData { + +// A convenient default parameter for class template Site below +struct Base +{ + virtual ~Base() {} +}; + +// Need a truly one-argument alias template below for the default +// template-template argument of Site +// (unique_ptr has two, the second is defaulted) +template< typename Object > using UniquePtr = std::unique_ptr< Object >; + +// A convenient base class defining abstract virtual Clone() for a given kind +// of pointer +template< + template class Owner = UniquePtr +> struct Cloneable +{ + using Base = Cloneable; + using PointerType = Owner< Base >; + + virtual ~Cloneable() {} + virtual PointerType Clone() const = 0; +}; + +/* + \brief ClientData::Site class template enables decoupling of the + implementation of core data structures, inheriting it, from compilation and + link dependency on the details of other user interface modules, while also + allowing the latter to associate their own extra data caches with each + instance of the core class, and the caches can have a coterminous lifetime. + + This can implement an "observer pattern" in which the core object pushes + notifications to client-side handlers, or it can just implement storage + and retrieval services for the client. + + typical usage pattern in core code: + + class Host; + class AbstractClientData // Abstract base class for attached data + { + virtual ~AbstractClientData(); // minimum for memory management + + // optional extra observer protocols + virtual void NotificationMethod( + // maybe host passes reference to self, maybe not + // Host &host + ) = 0; + }; + + class Host + : public ClientData::Site< Host, AbstractClientData > + // That inheritance is a case of CRTP + // (the "curiously recurring template pattern") + // in which the base class takes the derived class as a template argument + { + public: + Host() + { + // If using an Observer protocol, force early creation of all client + // data: + BuildAll(); + } + + void NotifyAll() + { + // Visit all non-null objects + ForEach( []( AbstractClientData &data ){ + data.NotificationMethod( + // *this + ); + } ); + } + } + + typical usage pattern in client module -- observer pattern, and retrieval + + class MyClientData : public AbstractClientData + { + public: + MyClientData( Host &host ) + { + // ... use host, maybe keep a back pointer to it, maybe not, + // depending how Host declares NotificationMethod ... + // ... Maybe Host too is an abstract class and we invoke some + // virtual function of it ... + } + void NotificationMethod( + // Host &host + ) override + { + // ... Observer actions + // (If there is more than one separately compiled module using this + // protocol, beware that the sequence of notifications is unspecified) + } + + private: + int mExtraStuff; + }; + + // Registration of a factory at static initialization time, to be called + // when a Host uses BuildAll, or else lazily when client code uses + // Host::Get() + static const Host::RegisteredFactory key{ + []( Host &host ){ return std::make_unique< MyClientData >( host ); } + }; + + // Use of that key at other times, not dependent on notifications from + // the core + void DoSomething( Host &host ) + { + // This may force lazy construction of ClientData, always returning + // an object (or else throwing) + auto &data = host.Get< MyClientData >( key ); + auto val = data.mExtraStuff; + // ... + } + + void DoAnotherThing( Host &host ) + { + // Does not force lazy construction of ClientData + auto *pData = host.Find< MyClientData >( key ); + if ( pData ) { + auto val = data.mExtraStuff; + // ... + } + } + + void DoYetAnotherThing( Host &host ) + { + // Reassign the pointer in this client's slot + host.Assign( key, MyReplacementObject( host ) ); + } + + About laziness: If the client only needs retrieval, it might need + construction of data only on demand. But if the host is meant to push + notifications to the clients, then the host class is responsible for forcing + early building of all ClientData when host is constructed, as in the example + above. + + About unusual registration sequences: if registration of a factory + happens after some host objects are already in existence, their associated + client data fail to be created if you rely only on BuildAll in the Host + constructor. Early deregistration of factories is permitted, in which case + any later constructed host objects will carry null pointers in the associated + slot, and a small "leak" in the space of per-host slots will persist until + the program exits. All such usage is not recommended. +*/ + +template< + typename Host, + typename ClientData = Base, + + // Remaining parameters are often defaulted + + CopyingPolicy ObjectCopyingPolicy = SkipCopying, + + // The kind of pointer Host will hold to ClientData; you might want to + // use std::shared_ptr, std::weak_ptr, or wxWeakRef instead + template class Pointer = UniquePtr, + + // Thread safety policies for the tables of ClientData objects in each Host + // object, and for the static factory function registry + LockingPolicy ObjectLockingPolicy = NoLocking, + LockingPolicy RegistryLockingPolicy = NoLocking +> +class Site +{ +public: + ~Site() + { + static_assert( std::has_virtual_destructor::value, + "ClientData::Site requires a data class with a virtual destructor" ); + } + + // Associated type aliases + using DataType = ClientData; + using DataPointer = Pointer< ClientData >; + using DataFactory = std::function< DataPointer( Host& ) >; + + Site() + { + auto factories = GetFactories(); + auto size = factories.mObject.size(); + mData.reserve( size ); + } + Site( const Site &other ) + : mData( other.mData ) + { } + Site& operator =( const Site & other ) + { mData = other.mData; return *this; } + Site( Site && other ) + : mData( std::move(other.mData) ) + { } + Site& operator =( Site && other ) + { mData = std::move(other.mData); return *this; } + + /// \brief a type meant to be stored by client code in a static variable, + /// and used as a retrieval key to get the manufactured client object back + /// from the host object. + /// It can be destroyed to de-register the factory, but usually not before + /// destruction of statics at program exit. + class RegisteredFactory + { + public: + RegisteredFactory( DataFactory factory ) + { + auto factories = GetFactories(); + mIndex = factories.mObject.size(); + factories.mObject.emplace_back( std::move( factory ) ); + } + RegisteredFactory( RegisteredFactory &&other ) + { + mIndex = other.mIndex; + mOwner = other.mOwner; + other.mOwner = false; + } + ~RegisteredFactory() + { + if (mOwner) { + auto factories = GetFactories(); + // Should always be true, the factory vector never shrinks: + if ( mIndex < factories.mObject.size() ) + factories.mObject[mIndex] = nullptr; + } + } + private: + friend Site; + bool mOwner{ true }; + size_t mIndex; + }; + + // member functions for use by clients + + // \brief Get a reference to an object, creating it on demand if needed, and + // down-cast it with static_cast. Throws on failure to create it. + // (Lifetime of the object may depend on the Host's lifetime and also on the + // client's use of Assign(). Site is not responsible for guarantees.) + template< typename Subclass = ClientData > + Subclass &Get( const RegisteredFactory &key ) + { + auto data = GetData(); + return DoGet< Subclass >( data, key ); + } + + // const counterpart of the previous + template< typename Subclass = const ClientData > + auto Get( const RegisteredFactory &key ) const -> typename + std::enable_if< std::is_const< Subclass >::value, Subclass & >::type + { + auto data = GetData(); + return DoGet< Subclass >( data, key ); + } + + // \brief Get a (bare) pointer to an object, or null, and down-cast it with + // static_cast. Do not create any object. + // (Lifetime of the object may depend on the Host's lifetime and also on the + // client's use of Assign(). Site is not responsible for guarantees.) + template< typename Subclass = ClientData > + Subclass *Find( const RegisteredFactory &key ) + { + auto data = GetData(); + return DoFind< Subclass >( data, key ); + } + + // const counterpart of the previous + template< typename Subclass = const ClientData > + auto Find( const RegisteredFactory &key ) const -> typename + std::enable_if< std::is_const< Subclass >::value, Subclass * >::type + { + auto data = GetData(); + return DoFind< Subclass >( data, key ); + } + + // \brief Reassign Host's pointer to ClientData. + // If there is object locking, then reassignments to the same slot in the + // same host object are serialized. + template< typename ReplacementPointer > + void Assign( const RegisteredFactory &key, ReplacementPointer &&replacement ) + { + auto index = key.mIndex; + auto data = GetData(); + EnsureIndex( data, index ); + auto iter = GetIterator( data, index ); + // Copy or move as appropriate: + *iter = std::forward< ReplacementPointer >( replacement ); + } + +protected: + // member functions for use by Host + + // \brief Invoke function on each ClientData object that has been created in + // this, but do not cause the creation of any. + template< typename Function > + void ForEach( const Function &function ) + { + auto data = GetData(); + for( auto &pObject : data.mObject ) { + const auto &ptr = Dereferenceable(pObject); + if ( ptr ) + function( *ptr ); + } + } + + // const counterpart of previous, only compiles with a function that takes + // a value or const reference argument + template< typename Function > + void ForEach( const Function &function ) const + { + auto data = GetData(); + for( auto &pObject : data.mObject ) { + const auto &ptr = Dereferenceable(pObject); + if ( ptr ) { + const auto &c_ref = *ptr; + function( c_ref ); + } + } + } + + // \brief For each registered factory, if the corresponding object in this + // is absent, then invoke the factory and store the result. + void BuildAll() + { + // Note that we cannot call this function in the Site constructor as we + // might wish, because we pass *this to the factories, but this is not yet + // fully constructed as the ultimate derived class. So delayed calls to + // this function are needed. + size_t size; + { + auto factories = GetFactories(); + size = factories.mObject.size(); + // Release lock on factories before getting one on data -- otherwise + // there would be a deadlock possibility inside Ensure + } + auto data = GetData(); + EnsureIndex( data, size - 1 ); + auto iter = GetIterator( data, 0 ); + for ( size_t ii = 0; ii < size; ++ii, ++iter ) + static_cast< void >( Build( data, iter, ii ) ); + } + +private: + using DataFactories = + Lockable< std::vector< DataFactory >, RegistryLockingPolicy >; + using DataContainer = + Lockable< + Copyable< std::vector< DataPointer >, ObjectCopyingPolicy >, + ObjectLockingPolicy + >; + + decltype( Dereferenceable( std::declval() ) ) + Slot( Locked &data, const RegisteredFactory &key, bool create ) + { + auto index = key.mIndex; + EnsureIndex( data, index ); + auto iter = GetIterator( data, index ); + auto &pointer = create ? Build( data, iter, index ) : *iter; + return Dereferenceable( pointer ); + } + + template< typename Subclass > + Subclass &DoGet( Locked &data, const RegisteredFactory &key ) + { + const auto &d = Slot( data, key, true ); + if (!d) + // Oops, a factory was deregistered too soon, or returned a null, or + // the pointer was reassigned null + THROW_INCONSISTENCY_EXCEPTION; + return static_cast< Subclass& >( *d ); + } + + template< typename Subclass > + Subclass *DoFind( Locked &data, const RegisteredFactory &key ) + { + const auto &d = Slot( data, key, false ); + if (!d) + return nullptr; + else + return static_cast< Subclass* >( &*d ); + } + + static Locked< DataFactories > GetFactories() + { + // C++11 does not need extra thread synch for static initialization + // Note that linker eliminates duplicates of this function + static DataFactories factories; + + // But give back a scoped lock to the user of this function, in case + // there is contention to resize the vector + return Locked< DataFactories >{ factories }; + } + + Locked GetData() + { + return Locked< DataContainer >{ mData }; + } + + static void EnsureIndex( Locked &data, size_t index ) + { + if (data.mObject.size() <= index) + data.mObject.resize(index + 1); + } + + static typename DataContainer::iterator inline + GetIterator( Locked &data, size_t index ) + { + // This function might help generalize Site with different kinds of + // containers for pointers to ClientData that are not random-access. + // Perhaps non-relocation of elements will be needed. + // Perhaps another template-template parameter could vary the kind of + // container. + auto result = data.mObject.begin(); + std::advance( result, index ); + return result; + } + + DataPointer &Build( Locked &, + typename DataContainer::iterator iter, size_t index ) + { + // If there is no object at index, then invoke the factory, else do + // nothing. + // The factory may be null or may return null, in which case do nothing. + auto &result = *iter; + if (!Dereferenceable(result)) { + // creation on demand + auto factories = GetFactories(); + auto &factory = factories.mObject[index]; + result = factory + ? factory( static_cast< Host& >( *this ) ) + : DataPointer{}; + } + return result; + } + + // Container of pointers returned by factories, per instance of Host class + // This is the only non-static data member that Site injects into the + // derived class. + DataContainer mData; +}; + +} + +#endif diff --git a/src/ClientDataHelpers.h b/src/ClientDataHelpers.h new file mode 100644 index 000000000..3ab08c152 --- /dev/null +++ b/src/ClientDataHelpers.h @@ -0,0 +1,139 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +ClientDataHelpers.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_CLIENT_DATA_HELPERS__ +#define __AUDACITY_CLIENT_DATA_HELPERS__ + +#include +#include +#include + +namespace ClientData { + // Helpers to define ClientData::Site class template + +// To specify (separately for the table of factories, and for the per-Site +// container of client data objects) whether to ensure mutual exclusion. +enum LockingPolicy { + NoLocking, + NonrecursiveLocking, // using std::mutex + RecursiveLocking, // using std::recursive_mutex +}; + +// To specify how the Site implements its copy constructor and assignment. +// (Move construction and assignment always work.) +enum CopyingPolicy { + SkipCopying, // copy ignores the argument and constructs empty + ShallowCopying, // just copy smart pointers; won't compile for unique_ptr + DeepCopying, // requires ClientData to define a Clone() member; + // won't compile for weak_ptr (and wouldn't work) +}; + +// forward declarations +struct Base; +template< + template class Owner +> struct Cloneable; + +// A conversion so we can use operator * in all the likely cases for the +// template parameter Pointer. (Return value should be bound only to const +// lvalue references) +template< typename Ptr > static inline + const Ptr &Dereferenceable( Ptr &p ) + { return p; } // returns an lvalue +template< typename Obj > static inline + std::shared_ptr Dereferenceable( std::weak_ptr &p ) + { return p.lock(); } // overload returns a prvalue + +// Decorator template to implement locking policies +template< typename Object, LockingPolicy > struct Lockable; +template< typename Object > struct Lockable< Object, NoLocking > +: Object { + // implement trivial non-locking policy + struct Lock{}; + Lock lock() { return {}; } +}; +template< typename Object > struct Lockable< Object, NonrecursiveLocking > +: Object, std::mutex { + // implement real locking + using Lock = std::unique_lock< std::mutex >; + Lock lock() { return Lock{ *this }; } +}; +template< typename Object > struct Lockable< Object, RecursiveLocking > +: Object, std::recursive_mutex { + // implement real locking + using Lock = std::unique_lock< std::recursive_mutex >; + Lock lock() { return Lock{ *this }; } +}; + +// Pairing of a reference to a Lockable and a lock on it +template< typename Lockable > struct Locked + // inherit, maybe empty base class optimization applies: + : private Lockable::Lock +{ + explicit Locked( Lockable &object ) + : Lockable::Lock( object.lock() ) + , mObject( object ) + {} + Lockable &mObject; +}; + +// Decorator template implements the copying policy +template< typename Container, CopyingPolicy > struct Copyable; +template< typename Container > struct Copyable< Container, SkipCopying > +: Container { + Copyable() = default; + Copyable( const Copyable & ) {} + Copyable &operator=( const Copyable & ) { return *this; } + Copyable( Copyable && ) = default; + Copyable &operator=( Copyable&& ) = default; +}; +template< typename Container > struct Copyable< Container, ShallowCopying > +: Container { + Copyable() = default; + Copyable( const Copyable &other ) + { *this = other; } + Copyable &operator=( const Copyable &other ) + { + if (this != &other) { + // Build then swap for strong exception guarantee + Copyable temp; + for ( auto &&ptr : other ) + temp.push_back( ptr ); + this->swap( temp ); + } + return *this; + } + Copyable( Copyable && ) = default; + Copyable &operator=( Copyable&& ) = default; +}; +template< typename Container > struct Copyable< Container, DeepCopying > +: Container { + Copyable() = default; + Copyable( const Copyable &other ) + { *this = other; } + Copyable &operator=( const Copyable &other ) + { + if (this != &other) { + // Build then swap for strong exception guarantee + Copyable temp; + for ( auto &&p : other ) { + temp.push_back( p ? p->Clone() : (decltype( p->Clone() )){} ); + } + this->swap( temp ); + } + return *this; + } + Copyable( Copyable && ) = default; + Copyable &operator=( Copyable&& ) = default; +}; + +} + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index a90377f67..e26ead79c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -125,6 +125,8 @@ audacity_SOURCES = \ Benchmark.h \ CellularPanel.cpp \ CellularPanel.h \ + ClientData.h \ + ClientDataHelpers.h \ Clipboard.cpp \ Clipboard.h \ CrashReport.cpp \ diff --git a/src/Makefile.in b/src/Makefile.in index 287a8dd22..bb7f369a0 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -297,29 +297,30 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \ AutoRecovery.h BatchCommandDialog.cpp BatchCommandDialog.h \ BatchCommands.cpp BatchCommands.h BatchProcessDialog.cpp \ BatchProcessDialog.h Benchmark.cpp Benchmark.h \ - CellularPanel.cpp CellularPanel.h Clipboard.cpp Clipboard.h \ - CrashReport.cpp CrashReport.h Dependencies.cpp Dependencies.h \ - DeviceChange.cpp DeviceChange.h DeviceManager.cpp \ - DeviceManager.h Diags.cpp Diags.h Envelope.cpp Envelope.h \ - Experimental.h FFmpeg.cpp FFmpeg.h FFT.cpp FFT.h \ - FileException.cpp FileException.h FileIO.cpp FileIO.h \ - FileNames.cpp FileNames.h float_cast.h FreqWindow.cpp \ - FreqWindow.h HelpText.cpp HelpText.h HistoryWindow.cpp \ - HistoryWindow.h HitTestResult.h ImageManipulation.cpp \ - ImageManipulation.h InconsistencyException.cpp \ - InconsistencyException.h InterpolateAudio.cpp \ - InterpolateAudio.h KeyboardCapture.cpp KeyboardCapture.h \ - LabelDialog.cpp LabelDialog.h LabelTrack.cpp LabelTrack.h \ - LangChoice.cpp LangChoice.h Languages.cpp Languages.h \ - Legacy.cpp Legacy.h Lyrics.cpp Lyrics.h LyricsWindow.cpp \ - LyricsWindow.h MacroMagic.h Matrix.cpp Matrix.h MemoryX.h \ - Menus.cpp Menus.h MissingAliasFileDialog.cpp \ - MissingAliasFileDialog.h Mix.cpp Mix.h MixerBoard.cpp \ - MixerBoard.h ModuleManager.cpp ModuleManager.h NumberScale.h \ - PitchName.cpp PitchName.h PlatformCompatibility.cpp \ - PlatformCompatibility.h PluginManager.cpp PluginManager.h \ - Printing.cpp Printing.h Profiler.cpp Profiler.h Project.cpp \ - Project.h ProjectFileIORegistry.cpp ProjectFileIORegistry.h \ + CellularPanel.cpp CellularPanel.h ClientData.h \ + ClientDataHelpers.h Clipboard.cpp Clipboard.h CrashReport.cpp \ + CrashReport.h Dependencies.cpp Dependencies.h DeviceChange.cpp \ + DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp \ + Diags.h Envelope.cpp Envelope.h Experimental.h FFmpeg.cpp \ + FFmpeg.h FFT.cpp FFT.h FileException.cpp FileException.h \ + FileIO.cpp FileIO.h FileNames.cpp FileNames.h float_cast.h \ + FreqWindow.cpp FreqWindow.h HelpText.cpp HelpText.h \ + HistoryWindow.cpp HistoryWindow.h HitTestResult.h \ + ImageManipulation.cpp ImageManipulation.h \ + InconsistencyException.cpp InconsistencyException.h \ + InterpolateAudio.cpp InterpolateAudio.h KeyboardCapture.cpp \ + KeyboardCapture.h LabelDialog.cpp LabelDialog.h LabelTrack.cpp \ + LabelTrack.h LangChoice.cpp LangChoice.h Languages.cpp \ + Languages.h Legacy.cpp Legacy.h Lyrics.cpp Lyrics.h \ + LyricsWindow.cpp LyricsWindow.h MacroMagic.h Matrix.cpp \ + Matrix.h MemoryX.h Menus.cpp Menus.h \ + MissingAliasFileDialog.cpp MissingAliasFileDialog.h Mix.cpp \ + Mix.h MixerBoard.cpp MixerBoard.h ModuleManager.cpp \ + ModuleManager.h NumberScale.h PitchName.cpp PitchName.h \ + PlatformCompatibility.cpp PlatformCompatibility.h \ + PluginManager.cpp PluginManager.h Printing.cpp Printing.h \ + Profiler.cpp Profiler.h Project.cpp Project.h \ + ProjectFileIORegistry.cpp ProjectFileIORegistry.h \ ProjectFSCK.cpp ProjectFSCK.h RealFFTf.cpp RealFFTf.h \ RealFFTf48x.cpp RealFFTf48x.h RefreshCode.h Resample.cpp \ Resample.h RevisionIdent.h RingBuffer.cpp RingBuffer.h \ @@ -1358,29 +1359,30 @@ audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \ AutoRecovery.h BatchCommandDialog.cpp BatchCommandDialog.h \ BatchCommands.cpp BatchCommands.h BatchProcessDialog.cpp \ BatchProcessDialog.h Benchmark.cpp Benchmark.h \ - CellularPanel.cpp CellularPanel.h Clipboard.cpp Clipboard.h \ - CrashReport.cpp CrashReport.h Dependencies.cpp Dependencies.h \ - DeviceChange.cpp DeviceChange.h DeviceManager.cpp \ - DeviceManager.h Diags.cpp Diags.h Envelope.cpp Envelope.h \ - Experimental.h FFmpeg.cpp FFmpeg.h FFT.cpp FFT.h \ - FileException.cpp FileException.h FileIO.cpp FileIO.h \ - FileNames.cpp FileNames.h float_cast.h FreqWindow.cpp \ - FreqWindow.h HelpText.cpp HelpText.h HistoryWindow.cpp \ - HistoryWindow.h HitTestResult.h ImageManipulation.cpp \ - ImageManipulation.h InconsistencyException.cpp \ - InconsistencyException.h InterpolateAudio.cpp \ - InterpolateAudio.h KeyboardCapture.cpp KeyboardCapture.h \ - LabelDialog.cpp LabelDialog.h LabelTrack.cpp LabelTrack.h \ - LangChoice.cpp LangChoice.h Languages.cpp Languages.h \ - Legacy.cpp Legacy.h Lyrics.cpp Lyrics.h LyricsWindow.cpp \ - LyricsWindow.h MacroMagic.h Matrix.cpp Matrix.h MemoryX.h \ - Menus.cpp Menus.h MissingAliasFileDialog.cpp \ - MissingAliasFileDialog.h Mix.cpp Mix.h MixerBoard.cpp \ - MixerBoard.h ModuleManager.cpp ModuleManager.h NumberScale.h \ - PitchName.cpp PitchName.h PlatformCompatibility.cpp \ - PlatformCompatibility.h PluginManager.cpp PluginManager.h \ - Printing.cpp Printing.h Profiler.cpp Profiler.h Project.cpp \ - Project.h ProjectFileIORegistry.cpp ProjectFileIORegistry.h \ + CellularPanel.cpp CellularPanel.h ClientData.h \ + ClientDataHelpers.h Clipboard.cpp Clipboard.h CrashReport.cpp \ + CrashReport.h Dependencies.cpp Dependencies.h DeviceChange.cpp \ + DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp \ + Diags.h Envelope.cpp Envelope.h Experimental.h FFmpeg.cpp \ + FFmpeg.h FFT.cpp FFT.h FileException.cpp FileException.h \ + FileIO.cpp FileIO.h FileNames.cpp FileNames.h float_cast.h \ + FreqWindow.cpp FreqWindow.h HelpText.cpp HelpText.h \ + HistoryWindow.cpp HistoryWindow.h HitTestResult.h \ + ImageManipulation.cpp ImageManipulation.h \ + InconsistencyException.cpp InconsistencyException.h \ + InterpolateAudio.cpp InterpolateAudio.h KeyboardCapture.cpp \ + KeyboardCapture.h LabelDialog.cpp LabelDialog.h LabelTrack.cpp \ + LabelTrack.h LangChoice.cpp LangChoice.h Languages.cpp \ + Languages.h Legacy.cpp Legacy.h Lyrics.cpp Lyrics.h \ + LyricsWindow.cpp LyricsWindow.h MacroMagic.h Matrix.cpp \ + Matrix.h MemoryX.h Menus.cpp Menus.h \ + MissingAliasFileDialog.cpp MissingAliasFileDialog.h Mix.cpp \ + Mix.h MixerBoard.cpp MixerBoard.h ModuleManager.cpp \ + ModuleManager.h NumberScale.h PitchName.cpp PitchName.h \ + PlatformCompatibility.cpp PlatformCompatibility.h \ + PluginManager.cpp PluginManager.h Printing.cpp Printing.h \ + Profiler.cpp Profiler.h Project.cpp Project.h \ + ProjectFileIORegistry.cpp ProjectFileIORegistry.h \ ProjectFSCK.cpp ProjectFSCK.h RealFFTf.cpp RealFFTf.h \ RealFFTf48x.cpp RealFFTf48x.h RefreshCode.h Resample.cpp \ Resample.h RevisionIdent.h RingBuffer.cpp RingBuffer.h \ diff --git a/src/Project.cpp b/src/Project.cpp index 280eeb0a2..59a4b4b9b 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -996,64 +996,6 @@ static wxString CreateUniqueName() wxString::Format(wxT(" N-%i"), ++count); } -namespace { - -#if 0 -std::mutex sObjectFactoriesMutex; -struct ObjectFactorySetLocker : private std::unique_lock< std::mutex > -{ - ObjectFactorySetLocker() - : std::unique_lock< std::mutex > { sObjectFactoriesMutex } - {} -}; -#else -struct ObjectFactorySetLocker {}; -#endif - -std::vector &sObjectFactories() -{ - // Put this declaration inside a function to avoid problems of undefined - // sequence of initialization of file-scope statics in different - // compilation units. - // Note that mutex locking is not needed for constructing a static object - // in C++11: - //https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables - static std::vector factories; - return factories; -} -} - -AudacityProject:: -RegisteredAttachedObjectFactory::RegisteredAttachedObjectFactory( - const AttachedObjectFactory &factory ) -{ - ObjectFactorySetLocker locker; - mIndex = sObjectFactories().size(); - sObjectFactories().push_back( factory ); - - // In case registration happens while projects exist: - for (const auto &pProject : gAudacityProjects) { - if (pProject->mAttachedObjects.size() == mIndex) { - auto pObject = factory(); - wxASSERT( pObject ); - pProject->mAttachedObjects.push_back( std::move( pObject ) ); - } - } -} - -AudacityProject:: -AttachedObject &AudacityProject::GetAttachedObject( - const RegisteredAttachedObjectFactory &factory ) -{ - ObjectFactorySetLocker locker; - if ( factory.mIndex >= mAttachedObjects.size() ) - THROW_INCONSISTENCY_EXCEPTION; - auto &pObject = mAttachedObjects[ factory.mIndex ]; - if ( !pObject ) - THROW_INCONSISTENCY_EXCEPTION; - return *pObject; -} - enum { FirstID = 1000, @@ -1163,15 +1105,6 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, // Initialize view info (shared with TrackPanel) // - { - ObjectFactorySetLocker locker; - for (const auto &factory : sObjectFactories()) { - auto pObject = factory(); - wxASSERT( pObject ); - mAttachedObjects.push_back( std::move( pObject ) ); - } - } - mMenuManager = std::make_unique(); mLockPlayRegion = false; @@ -1454,6 +1387,8 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, #ifdef EXPERIMENTAL_DA2 ClearBackground();// For wxGTK. #endif + + AttachedObjects::BuildAll(); } AudacityProject::~AudacityProject() diff --git a/src/Project.h b/src/Project.h index eac8cd477..f0ee5a31a 100644 --- a/src/Project.h +++ b/src/Project.h @@ -22,6 +22,7 @@ #include "Experimental.h" +#include "ClientData.h" #include "Track.h" #include "Prefs.h" #include "SelectionState.h" @@ -172,6 +173,10 @@ class WaveTrack; class MenuManager; +using AttachedObjects = ClientData::Site< + AudacityProject, ClientData::Base, ClientData::SkipCopying, std::shared_ptr +>; + class AUDACITY_DLL_API AudacityProject final : public wxFrame, public TrackPanelListener, public SelectionBarListener, @@ -179,8 +184,11 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, public XMLTagHandler, public AudioIOListener, private PrefsListener + , public AttachedObjects { public: + using AttachedObjects = ::AttachedObjects; + AudacityProject(wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size); virtual ~AudacityProject(); @@ -188,24 +196,6 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, // Next available ID for sub-windows int NextWindowID(); - using AttachedObject = wxObject; - using AttachedObjectFactory = - std::function< std::unique_ptr() >; - - // Typically a static object. Allows various application code to - // attach per-project state, without Project.cpp needing to include a header - // file or know the details. - class RegisteredAttachedObjectFactory { - public: - RegisteredAttachedObjectFactory( const AttachedObjectFactory &factory ); - - private: - friend AudacityProject; - size_t mIndex {}; - }; - AttachedObject & - GetAttachedObject( const RegisteredAttachedObjectFactory& factory ); - virtual void ApplyUpdatedTheme(); AudioIOStartStreamOptions GetDefaultPlayOptions(); @@ -724,7 +714,6 @@ private: #endif private: - std::vector< std::unique_ptr > mAttachedObjects; std::unique_ptr mMenuManager; public: diff --git a/src/menus/NavigationMenus.cpp b/src/menus/NavigationMenus.cpp index f6f4f8e0c..2168954c9 100644 --- a/src/menus/NavigationMenus.cpp +++ b/src/menus/NavigationMenus.cpp @@ -301,6 +301,7 @@ namespace NavigationActions { struct Handler : CommandHandlerObject // MUST be the first base class! + , ClientData::Base , PrefsListener { @@ -539,12 +540,12 @@ Handler() // Handler is stateful. Needs a factory registered with // AudacityProject. -static const AudacityProject::RegisteredAttachedObjectFactory factory{ []{ - return std::make_unique< NavigationActions::Handler >(); -} }; +static const AudacityProject::AttachedObjects::RegisteredFactory key{ + [](AudacityProject&) { + return std::make_unique< NavigationActions::Handler >(); } }; + static CommandHandlerObject &findCommandHandler(AudacityProject &project) { - return static_cast( - project.GetAttachedObject(factory)); + return project.AttachedObjects::Get< NavigationActions::Handler >( key ); }; // Menu definitions diff --git a/src/menus/SelectMenus.cpp b/src/menus/SelectMenus.cpp index 54e489a1b..5c1712d5d 100644 --- a/src/menus/SelectMenus.cpp +++ b/src/menus/SelectMenus.cpp @@ -553,6 +553,7 @@ void DoSelectSomething(AudacityProject &project) struct Handler : CommandHandlerObject // MUST be the first base class! + , ClientData::Base , PrefsListener { @@ -1133,12 +1134,12 @@ Handler() // Handler is stateful. Needs a factory registered with // AudacityProject. -static const AudacityProject::RegisteredAttachedObjectFactory factory{ []{ - return std::make_unique< SelectActions::Handler >(); -} }; +static const AudacityProject::AttachedObjects::RegisteredFactory key{ + [](AudacityProject&) { + return std::make_unique< SelectActions::Handler >(); } }; + static CommandHandlerObject &findCommandHandler(AudacityProject &project) { - return static_cast( - project.GetAttachedObject(factory)); + return project.AttachedObjects::Get< SelectActions::Handler >( key ); }; // Menu definitions diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 08af97e03..7bba31656 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -505,6 +505,8 @@ + + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index d814b88bf..e67db9739 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -2251,6 +2251,12 @@ src + + src + + + src + src