1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-29 06:38:38 +02:00

No hidden dependencies on TrackView & TrackControls subclasses...

... from the Track subclasses, caused by splitting their virtual function
definitions, making dependencies that scripts/graph.pl did not detect.

Instead use a new system for registering tables of factory functions and
type-switching on the Track objects' RTTI to get to the right function.
This commit is contained in:
Paul Licameli 2019-06-21 09:12:20 -04:00
commit 3e4480ce6f
22 changed files with 376 additions and 76 deletions

View File

@ -30,6 +30,7 @@ src/AdornedRulerPanel.h
src/AboutDialog.cpp
src/AboutDialog.h
src/AllThemeResources.h
src/AttachedVirtualFunction.h
src/Audacity.h
src/AudacityApp.cpp
src/AudacityApp.h

View File

@ -3364,6 +3364,7 @@
5ED1D0AF1CDE560C00471E3C /* BackedPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BackedPanel.cpp; sourceTree = "<group>"; };
5ED1D0B01CDE560C00471E3C /* BackedPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BackedPanel.h; sourceTree = "<group>"; };
5EE8984821D68D88006DE939 /* CommandManagerWindowClasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommandManagerWindowClasses.h; sourceTree = "<group>"; };
5EEE942021F397C00038E68E /* AttachedVirtualFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachedVirtualFunction.h; sourceTree = "<group>"; };
5EF17C211D1F0A690090A642 /* ScrubbingToolBar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScrubbingToolBar.cpp; sourceTree = "<group>"; };
5EF17C221D1F0A690090A642 /* ScrubbingToolBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrubbingToolBar.h; sourceTree = "<group>"; };
5EF3E643203FBAFB006C6882 /* AudacityCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudacityCommand.cpp; sourceTree = "<group>"; };
@ -4470,6 +4471,7 @@
1790AFCA09883BFD008A330A /* AColor.h */,
5E08C7382180D460004079AE /* AdornedRulerPanel.h */,
28FB12230A3790DF006F0917 /* AllThemeResources.h */,
5EEE942021F397C00038E68E /* AttachedVirtualFunction.h */,
1790AFCC09883BFD008A330A /* Audacity.h */,
1790AFCF09883BFD008A330A /* AudacityApp.h */,
5ECCE7651DE49834009900E9 /* AudacityException.h */,

View File

@ -0,0 +1,258 @@
/**********************************************************************
Audacity: A Digital Audio Editor
AttachedVirtualFunction.h
Paul Licameli
**********************************************************************/
#ifndef __AUDACITY_ATTACHED_VIRTUAL_FUNCTION__
#define __AUDACITY_ATTACHED_VIRTUAL_FUNCTION__
/* \brief Define a "virtual" function with multiple bodies chosen by type-switch
on the runtime class of the first argument, leaving the set of functions
open-ended for extension, but also requiring no modification of the definition
of the base class of the type hierarchy that is switched on.
The invocation of the function is not as efficient as for true virtual functions
but the advantage of this utility is greater compilation decoupling. A client
can attach its own virtual functions to a class hierarchy in the core.
Beware that invocation of the function should not be done during initialization
of file scope static objects. Dispatch might not go to the correct subclass
case if initializations are not yet complete.
Example usage:
////////
// Core classes in Host.h:
class AbstractHost
{
// ...
};
class SpecialHost : public Abstract Host
{
// ...
};
class ExtraSpecialHost : public SpecialHost
{
// ...
};
////////
// Declare the root of the attached function hierarchy in Client.h,
// which Host.cpp need not include at all:
enum class ErrorCode { Ok, Bad, // ...
}; // a return type for our function
// First this empty structure serving just to distinguish among instantiations
// of AttachedVirtualFunction with otherwise identical arguments
// An incomplete type is enough
struct DoSomethingTag;
// Now declare the "virtual function"
using DoSomething =
AttachedVirtualFunction<
DoSomethingTag,
ErrorCode,
AbstractHost, // class to be switched on by runtime type
int, double // other arguments
>;
////////
// Usage of the "virtual function"
#include "Client.h"
void UseDoSomething( AbstractHost &host )
{
// ...
auto error = DoSomething::Call( host, 0, 1.0 );
// ...
}
////////
// Out-of-line registrations needed in Client.cpp:
// Define the default function body here (as a function returning a function!)
template<> auto DoSomething::Implementation() -> Function {
return [](AbstractHost &host, int arg1, double arg2) {
return ErrorCode::Ok;
};
// or you could return nullptr to force InconsistencyException at runtime if
// the virtual function is invoked for a host subclass for which no override
// was defined
}
// Must also guarantee construction of an instance of class DoSomething at least
// once before any use of DoSomething::Call()
static DoSomething registerMe;
////////
// An override of the function is defined in SpecialClient.cpp,
// building up a hierarchy of function bodies parallel to the host class
// hierarchy
using DoSomethingSpecial = DoSomething::Override< SpecialHost >;
template<> template<> auto DoSomethingSpecial::Implementation() -> Function {
// The function can be defined assuming downcast of the first argument
// has been done
return [](SpecialHost &host, int arg1, double arg2) {
return arg1 == 0 ? ErrorCode::Ok : ErrorCode::Bad;
};
}
static DoSomethingSpecial registerMe;
////////
// A further override in ExtraSpecialClient.cpp, demonstrating call-through too
using DoSomethingExtraSpecial =
DoSomething::Override< ExtraSpecialHost, DoSomethingSpecial >;
template<> template<>
auto DoSomethingExtraSpecial::Implementation() -> Function {
return [](ExtraSpecialHost &host, int arg1, double arg2){
// Call the immediately overridden version of the function
auto result = Callthrough( host, arg1, arg2 );
if ( result == ErrorCode::OK ) {
if ( arg2 != 1066.0 )
result = ErrorCode::Bad;
}
return result;
};
}
static DoSomethinExtraSpecial registerMe;
*/
#include <functional>
#include <mutex>
#include <type_traits>
#include <utility>
#include "InconsistencyException.h"
template< typename Tag, typename Return, typename This, typename... Arguments >
class AttachedVirtualFunction
{
public:
// The type of an overriding function, taking a more specific first
// argument
template< typename Subclass >
using ImplementationFor = std::function< Return( Subclass&, Arguments... ) >;
// These member names are declared in this class template and redeclared
// in each override
using Object = This;
using Function = ImplementationFor< This >;
// A function returning a std::function, which you define elsewhere;
// it may return nullptr in case this must act somewhat as a "pure virtual",
// throwing InconsistencyException if the function is invoked on a subclass
// for which no override was defined
static Function Implementation();
// This class must be instantiated once at least to register the function in
// a table, but may be instantiated multiply (and will be if there are any
// overrides)
AttachedVirtualFunction()
{
static std::once_flag flag;
std::call_once( flag, []{ Register<This>( Implementation() ); } );
}
// For defining overrides of the virtual function; template arguments
// are the more specific subclass and the immediately overridden version
// of the function, defaulting to the base version
template<
typename Subclass, typename Overridden = AttachedVirtualFunction >
struct Override : Overridden
{
using Object = Subclass;
// Check that inheritance is correct
static_assert(
std::is_base_of< typename Overridden::Object, Object >::value,
"overridden class must be a base of the overriding class"
);
using Function = ImplementationFor< Subclass >;
// A function returning a std::function that must be defined out-of-line
static Function Implementation();
// May be used in the body of the overriding function, defining it in
// terms of the overridden one:
static Return Callthrough(
typename Overridden::Object &object, Arguments &&...arguments )
{
return Overridden::Implementation()(
object, std::forward< Arguments >( arguments )... );
}
Override()
{
static std::once_flag flag;
std::call_once( flag, []{
// Register in the table an adaptor thunk that downcasts the object
auto implementation = Implementation();
Register< Subclass >( [=]( This &obj, Arguments &&...arguments ){
return implementation(
static_cast< Subclass& >( obj ),
std::forward< Arguments >( arguments )... );
});
});
}
};
static Return Call( This &obj, Arguments &&...arguments )
{
try {
// Note that the constructors of this class and overrides cause
// the registry to be topologically sorted, with functions for
// less-derived classes earlier in the table; so take the last
// one that matches the object. (The object might not be of the exact
// class corresponding to any of the overrides, which is why this
// solution involves calling the predicates generated in Register,
// and wouldn't work just with hashing on std::type_index; but perhaps
// such a cache could be memo-ized)
auto &registry = GetRegistry();
auto iter = registry.rbegin(), end = registry.rend();
for ( ; iter != end; ++iter ) {
auto &entry = *iter;
if ( entry.predicate( &obj ) )
return entry.function(
obj, std::forward< Arguments >( arguments )... );
}
throw std::bad_function_call{};
}
catch ( const std::bad_function_call& ) {
// No matching case found with a non-null function.
// Translate the exception
THROW_INCONSISTENCY_EXCEPTION;
}
}
private:
template< typename Subclass >
static void Register( const ImplementationFor< This > &function )
{
// Push back a dynamic type test and corresponding function body
GetRegistry().push_back({
[]( This *b ){ return dynamic_cast< Subclass * >( b ) != nullptr; },
function
});
}
using Predicate = std::function< bool( This* ) >;
struct Entry
{
Predicate predicate;
ImplementationFor< This > function;
};
using Registry = std::vector< Entry >;
static Registry &GetRegistry()
{
static Registry registry;
return registry;
}
};
#endif

View File

@ -169,10 +169,6 @@ public:
double mClipLen;
int miLastLabel; // used by FindNextLabel and FindPrevLabel
private:
std::shared_ptr<TrackView> DoGetView() override;
std::shared_ptr<TrackControls> DoGetControls() override;
};
struct LabelTrackEvent : TrackListEvent

View File

@ -103,6 +103,7 @@ audacity_SOURCES = \
AdornedRulerPanel.cpp \
AdornedRulerPanel.h \
AllThemeResources.h \
AttachedVirtualFunction.h \
Audacity.h \
AudacityApp.cpp \
AudacityApp.h \

View File

@ -287,16 +287,17 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \
blockfile/SimpleBlockFile.cpp blockfile/SimpleBlockFile.h \
xml/XMLTagHandler.cpp xml/XMLTagHandler.h AboutDialog.cpp \
AboutDialog.h AColor.cpp AColor.h AdornedRulerPanel.cpp \
AdornedRulerPanel.h AllThemeResources.h Audacity.h \
AudacityApp.cpp AudacityApp.h AudacityException.cpp \
AudacityException.h AudacityLogger.cpp AudacityLogger.h \
AudioIO.cpp AudioIO.h AudioIOBase.cpp AudioIOBase.h \
AudioIOListener.h AutoRecovery.cpp AutoRecovery.h \
AutoRecoveryDialog.cpp AutoRecoveryDialog.h \
BatchCommandDialog.cpp BatchCommandDialog.h BatchCommands.cpp \
BatchCommands.h BatchProcessDialog.cpp BatchProcessDialog.h \
Benchmark.cpp Benchmark.h CellularPanel.cpp CellularPanel.h \
ClientData.h ClientDataHelpers.h Clipboard.cpp Clipboard.h \
AdornedRulerPanel.h AllThemeResources.h \
AttachedVirtualFunction.h Audacity.h AudacityApp.cpp \
AudacityApp.h AudacityException.cpp AudacityException.h \
AudacityLogger.cpp AudacityLogger.h AudioIO.cpp AudioIO.h \
AudioIOBase.cpp AudioIOBase.h AudioIOListener.h \
AutoRecovery.cpp AutoRecovery.h AutoRecoveryDialog.cpp \
AutoRecoveryDialog.h BatchCommandDialog.cpp \
BatchCommandDialog.h BatchCommands.cpp BatchCommands.h \
BatchProcessDialog.cpp BatchProcessDialog.h Benchmark.cpp \
Benchmark.h CellularPanel.cpp CellularPanel.h ClientData.h \
ClientDataHelpers.h Clipboard.cpp Clipboard.h \
CommonCommandFlags.cpp CommonCommandFlags.h CrashReport.cpp \
CrashReport.h Dependencies.cpp Dependencies.h DeviceChange.cpp \
DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp \
@ -1380,16 +1381,17 @@ audacity_LDADD = $(EXPAT_LIBS) $(FILEDIALOG_LIBS) $(PORTAUDIO_LIBS) \
$(am__append_43) $(am__append_45) $(am__append_48)
audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \
AboutDialog.h AColor.cpp AColor.h AdornedRulerPanel.cpp \
AdornedRulerPanel.h AllThemeResources.h Audacity.h \
AudacityApp.cpp AudacityApp.h AudacityException.cpp \
AudacityException.h AudacityLogger.cpp AudacityLogger.h \
AudioIO.cpp AudioIO.h AudioIOBase.cpp AudioIOBase.h \
AudioIOListener.h AutoRecovery.cpp AutoRecovery.h \
AutoRecoveryDialog.cpp AutoRecoveryDialog.h \
BatchCommandDialog.cpp BatchCommandDialog.h BatchCommands.cpp \
BatchCommands.h BatchProcessDialog.cpp BatchProcessDialog.h \
Benchmark.cpp Benchmark.h CellularPanel.cpp CellularPanel.h \
ClientData.h ClientDataHelpers.h Clipboard.cpp Clipboard.h \
AdornedRulerPanel.h AllThemeResources.h \
AttachedVirtualFunction.h Audacity.h AudacityApp.cpp \
AudacityApp.h AudacityException.cpp AudacityException.h \
AudacityLogger.cpp AudacityLogger.h AudioIO.cpp AudioIO.h \
AudioIOBase.cpp AudioIOBase.h AudioIOListener.h \
AutoRecovery.cpp AutoRecovery.h AutoRecoveryDialog.cpp \
AutoRecoveryDialog.h BatchCommandDialog.cpp \
BatchCommandDialog.h BatchCommands.cpp BatchCommands.h \
BatchProcessDialog.cpp BatchProcessDialog.h Benchmark.cpp \
Benchmark.h CellularPanel.cpp CellularPanel.h ClientData.h \
ClientDataHelpers.h Clipboard.cpp Clipboard.h \
CommonCommandFlags.cpp CommonCommandFlags.h CrashReport.cpp \
CrashReport.h Dependencies.cpp Dependencies.h DeviceChange.cpp \
DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp \

View File

@ -222,10 +222,6 @@ public:
int mVisibleChannels; // bit set of visible channels
std::weak_ptr<StretchHandle> mStretchHandle;
protected:
std::shared_ptr<TrackView> DoGetView() override;
std::shared_ptr<TrackControls> DoGetControls() override;
};
/// Data used to display a note track

View File

@ -113,10 +113,6 @@ private:
Track::Holder Clone() const override;
friend class TrackFactory;
protected:
std::shared_ptr<TrackView> DoGetView() override;
std::shared_ptr<TrackControls> DoGetControls() override;
};

View File

@ -1284,3 +1284,13 @@ void TrackFactory::Destroy( AudacityProject &project )
{
project.AttachedObjects::Assign( key2, nullptr );
}
template<> auto DoGetControls::Implementation() -> Function {
return nullptr;
}
static DoGetControls registerDoGetControls;
template<> auto DoGetView::Implementation() -> Function {
return nullptr;
}
static DoGetView registerDoGetView;

View File

@ -704,12 +704,6 @@ public:
bool HandleCommonXMLAttribute(const wxChar *attr, const wxChar *value);
protected:
// These are called to create controls on demand:
virtual std::shared_ptr<TrackView> DoGetView() = 0;
virtual std::shared_ptr<TrackControls> DoGetControls() = 0;
// These hold the controls:
std::shared_ptr<TrackView> mpView;
std::shared_ptr<CommonTrackCell> mpControls;
};
@ -1595,4 +1589,24 @@ class AUDACITY_DLL_API TrackFactory final
#endif
};
#include "AttachedVirtualFunction.h"
struct DoGetControlsTag;
using DoGetControls =
AttachedVirtualFunction<
DoGetControlsTag,
std::shared_ptr< TrackControls >,
Track
>;
struct DoGetViewTag;
using DoGetView =
AttachedVirtualFunction<
DoGetViewTag,
std::shared_ptr< TrackView >,
Track
>;
#endif

View File

@ -682,10 +682,6 @@ private:
std::unique_ptr<SpectrogramSettings> mpSpectrumSettings;
std::unique_ptr<WaveformSettings> mpWaveformSettings;
protected:
std::shared_ptr<TrackView> DoGetView() override;
std::shared_ptr<TrackControls> DoGetControls() override;
};
// This is meant to be a short-lived object, during whose lifetime,

View File

@ -170,3 +170,11 @@ PopupMenuTable *LabelTrackControls::GetMenuExtension(Track *)
{
return &LabelTrackMenuTable::Instance();
}
using DoGetLabelTrackControls = DoGetControls::Override< LabelTrack >;
template<> template<> auto DoGetLabelTrackControls::Implementation() -> Function {
return [](LabelTrack &track) {
return std::make_shared<LabelTrackControls>( track.SharedPointer() );
};
}
static DoGetLabelTrackControls registerDoGetLabelTrackControls;

View File

@ -2080,15 +2080,13 @@ int LabelTrackView::DialogForLabelName(
return status;
}
std::shared_ptr<TrackView> LabelTrack::DoGetView()
{
return std::make_shared<LabelTrackView>( SharedPointer() );
}
std::shared_ptr<TrackControls> LabelTrack::DoGetControls()
{
return std::make_shared<LabelTrackControls>( SharedPointer() );
using DoGetLabelTrackView = DoGetView::Override< LabelTrack >;
template<> template<> auto DoGetLabelTrackView::Implementation() -> Function {
return [](LabelTrack &track) {
return std::make_shared<LabelTrackView>( track.SharedPointer() );
};
}
static DoGetLabelTrackView registerDoGetLabelTrackView;
std::shared_ptr<TrackVRulerControls> LabelTrackView::DoGetVRulerControls()
{

View File

@ -310,3 +310,11 @@ void NoteTrackControls::ReCreateVelocitySlider( wxEvent &evt )
pParent;
#endif
}
using DoGetNoteTrackControls = DoGetControls::Override< NoteTrack >;
template<> template<> auto DoGetNoteTrackControls::Implementation() -> Function {
return [](NoteTrack &track) {
return std::make_shared<NoteTrackControls>( track.SharedPointer() );
};
}
static DoGetNoteTrackControls registerDoGetNoteTrackControls;

View File

@ -53,15 +53,13 @@ std::vector<UIHandlePtr> NoteTrackView::DetailedHitTest
return results;
}
std::shared_ptr<TrackView> NoteTrack::DoGetView()
{
return std::make_shared<NoteTrackView>( SharedPointer() );
}
std::shared_ptr<TrackControls> NoteTrack::DoGetControls()
{
return std::make_shared<NoteTrackControls>( SharedPointer() );
using DoGetNoteTrackView = DoGetView::Override< NoteTrack >;
template<> template<> auto DoGetNoteTrackView::Implementation() -> Function {
return [](NoteTrack &track) {
return std::make_shared<NoteTrackView>( track.SharedPointer() );
};
}
static DoGetNoteTrackView registerDoGetNoteTrackView;
std::shared_ptr<TrackVRulerControls> NoteTrackView::DoGetVRulerControls()
{

View File

@ -1283,3 +1283,11 @@ void WaveTrackControls::ReCreatePanSlider( wxEvent &event )
PAN_SLIDER);
gPanCaptured->SetDefaultValue(defPos);
}
using DoGetWaveTrackControls = DoGetControls::Override< WaveTrack >;
template<> template<> auto DoGetWaveTrackControls::Implementation() -> Function {
return [](WaveTrack &track) {
return std::make_shared<WaveTrackControls>( track.SharedPointer() );
};
}
static DoGetWaveTrackControls registerDoGetWaveTrackControls;

View File

@ -156,15 +156,13 @@ void WaveTrackView::DoSetMinimized( bool minimized )
TrackView::DoSetMinimized( minimized );
}
std::shared_ptr<TrackView> WaveTrack::DoGetView()
{
return std::make_shared<WaveTrackView>( SharedPointer() );
}
std::shared_ptr<TrackControls> WaveTrack::DoGetControls()
{
return std::make_shared<WaveTrackControls>( SharedPointer() );
using DoGetWaveTrackView = DoGetView::Override< WaveTrack >;
template<> template<> auto DoGetWaveTrackView::Implementation() -> Function {
return [](WaveTrack &track) {
return std::make_shared<WaveTrackView>( track.SharedPointer() );
};
}
static DoGetWaveTrackView registerDoGetWaveTrackView;
std::shared_ptr<TrackVRulerControls> WaveTrackView::DoGetVRulerControls()
{

View File

@ -170,3 +170,11 @@ PopupMenuTable *TimeTrackControls::GetMenuExtension(Track *)
{
return &TimeTrackMenuTable::Instance();
}
using DoGetTimeTrackControls = DoGetControls::Override< TimeTrack >;
template<> template<> auto DoGetTimeTrackControls::Implementation() -> Function {
return [](TimeTrack &track) {
return std::make_shared<TimeTrackControls>( track.SharedPointer() );
};
}
static DoGetTimeTrackControls registerDoGetTimeTrackControls;

View File

@ -43,15 +43,13 @@ std::vector<UIHandlePtr> TimeTrackView::DetailedHitTest
return results;
}
std::shared_ptr<TrackView> TimeTrack::DoGetView()
{
return std::make_shared<TimeTrackView>( SharedPointer() );
}
std::shared_ptr<TrackControls> TimeTrack::DoGetControls()
{
return std::make_shared<TimeTrackControls>( SharedPointer() );
using DoGetTimeTrackView = DoGetView::Override< TimeTrack >;
template<> template<> auto DoGetTimeTrackView::Implementation() -> Function {
return [](TimeTrack &track) {
return std::make_shared<TimeTrackView>( track.SharedPointer() );
};
}
static DoGetTimeTrackView registerDoGetTimeTrackView;
std::shared_ptr<TrackVRulerControls> TimeTrackView::DoGetVRulerControls()
{

View File

@ -57,7 +57,7 @@ std::shared_ptr<TrackView> Track::GetTrackView()
{
if (!mpView)
// create on demand
mpView = DoGetView();
mpView = DoGetView::Call( *this );
return mpView;
}
@ -70,7 +70,7 @@ std::shared_ptr<TrackPanelCell> Track::GetTrackControls()
{
if (!mpControls)
// create on demand
mpControls = DoGetControls();
mpControls = DoGetControls::Call( *this );
return mpControls;
}

View File

@ -508,6 +508,7 @@
<ClInclude Include="..\..\..\src\AColor.h" />
<ClInclude Include="..\..\..\src\AdornedRulerPanel.h" />
<ClInclude Include="..\..\..\src\AllThemeResources.h" />
<ClInclude Include="..\..\..\src\AttachedVirtualFunction.h" />
<ClInclude Include="..\..\..\src\Audacity.h" />
<ClInclude Include="..\..\..\src\AudacityApp.h" />
<ClInclude Include="..\..\..\src\AudacityException.h" />

View File

@ -1225,6 +1225,9 @@
<ClInclude Include="..\..\..\src\AllThemeResources.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\AttachedVirtualFunction.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\Audacity.h">
<Filter>src</Filter>
</ClInclude>