Commit 866c327d authored by Don Gagne's avatar Don Gagne

Merge pull request #1108 from DonLakeFlyer/Singletons

New Singleton Manager
parents efcde338 ae229451
......@@ -29,25 +29,7 @@
#include "Generic/GenericAutoPilotPlugin.h"
#include "QGCApplication.h"
AutoPilotPluginManager* AutoPilotPluginManager::_instance = NULL;
AutoPilotPluginManager* AutoPilotPluginManager::instance(void)
{
if (_instance == NULL) {
_instance = new AutoPilotPluginManager(qgcApp());
Q_CHECK_PTR(_instance);
}
Q_ASSERT(_instance);
return _instance;
}
void AutoPilotPluginManager::deleteInstance(void)
{
_instance = NULL;
delete this;
}
IMPLEMENT_QGC_SINGLETON(AutoPilotPluginManager, AutoPilotPluginManager)
AutoPilotPluginManager::AutoPilotPluginManager(QObject* parent) :
QGCSingleton(parent)
......
......@@ -41,13 +41,10 @@
class AutoPilotPluginManager : public QGCSingleton
{
Q_OBJECT
DECLARE_QGC_SINGLETON(AutoPilotPluginManager, AutoPilotPluginManager)
public:
/// @brief Returns the AutoPilotPluginManager singleton
static AutoPilotPluginManager* instance(void);
virtual void deleteInstance(void);
/// @brief Returns the singleton AutoPilot instance for the specified auto pilot type.
/// @param autopilotType Specified using the MAV_AUTOPILOT_* values.
AutoPilotPlugin* getInstanceForAutoPilotPlugin(int autopilotType);
......@@ -56,8 +53,6 @@ private:
/// All access to singleton is through AutoPilotPluginManager::instance
AutoPilotPluginManager(QObject* parent = NULL);
static AutoPilotPluginManager* _instance;
QMap<int, AutoPilotPlugin*> _pluginMap;
};
......
......@@ -54,7 +54,7 @@ void FlightModeConfigTest::init(void)
_mockUASManager = new MockUASManager();
Q_ASSERT(_mockUASManager);
UASManager::setMockUASManager(_mockUASManager);
UASManager::setMockInstance(_mockUASManager);
_mockUAS = new MockUAS();
Q_CHECK_PTR(_mockUAS);
......@@ -126,21 +126,21 @@ void FlightModeConfigTest::init(void)
void FlightModeConfigTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_configWidget);
delete _configWidget;
Q_ASSERT(_mockUAS);
delete _mockUAS;
UASManager::setMockUASManager(NULL);
UASManager::setMockInstance(NULL);
Q_ASSERT(_mockUASManager);
delete _mockUASManager;
_mapChannelCombo2Param.clear();
_mapChannelCombo2ButtonGroup.clear();
UnitTest::cleanup();
}
/// @brief Returns channel mapping for the specified parameters
......
......@@ -26,37 +26,16 @@
#include "FactSystem.h"
#include "UASManager.h"
#include "QGCApplication.h"
#include <QtQml>
FactSystem* FactSystem::_instance = NULL;
QMutex FactSystem::_singletonLock;
const char* FactSystem::_factSystemQmlUri = "QGroundControl.FactSystem";
FactSystem* FactSystem::instance(void)
{
if(_instance == 0) {
_singletonLock.lock();
if (_instance == 0) {
_instance = new FactSystem(qgcApp());
Q_CHECK_PTR(_instance);
}
_singletonLock.unlock();
}
Q_ASSERT(_instance);
return _instance;
}
IMPLEMENT_QGC_SINGLETON(FactSystem, FactSystem)
void FactSystem::deleteInstance(void)
{
_instance = NULL;
delete this;
}
const char* FactSystem::_factSystemQmlUri = "QGroundControl.FactSystem";
FactSystem::FactSystem(QObject* parent, bool registerSingleton) :
QGCSingleton(parent, registerSingleton)
FactSystem::FactSystem(QObject* parent) :
QGCSingleton(parent)
{
qmlRegisterType<Fact>(_factSystemQmlUri, 1, 0, "Fact");
qmlRegisterType<FactValidator>(_factSystemQmlUri, 1, 0, "FactValidator");
......
......@@ -33,8 +33,6 @@
#include "QGCSingleton.h"
#include "FactValidator.h"
#include <QMutex>
/// FactSystem is a singleton which provides access to the Facts in the system
///
/// The components of the FactSystem are a Fact which holds an individual value. FactMetaData holds
......@@ -47,22 +45,14 @@ class FactSystem : public QGCSingleton
{
Q_OBJECT
public:
/// Returns the FactSystem singleton
static FactSystem* instance(void);
/// Override from QGCSingleton
virtual void deleteInstance(void);
~FactSystem();
DECLARE_QGC_SINGLETON(FactSystem, FactSystem)
private:
/// All access to FactSystem is through FactSystem::instance, so constructor is private
FactSystem(QObject* parent = NULL, bool registerSingleton = true);
FactSystem(QObject* parent = NULL);
static QMutex _singletonLock; ///< Mutex to make calls to instance thread-safe
~FactSystem();
static FactSystem* _instance; ///< FactSystem singleton
static const char* _factSystemQmlUri; ///< URI for FactSystem QML imports
};
......
......@@ -175,7 +175,7 @@ QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) :
QGCApplication::~QGCApplication()
{
destroySingletonsForUnitTest();
_destroySingletons();
}
void QGCApplication::_initCommon(void)
......@@ -424,43 +424,33 @@ QGCApplication* qgcApp(void)
/// up being creating on something other than the main thread.
void QGCApplication::_createSingletons(void)
{
LinkManager* linkManager = LinkManager::instance();
// The order here is important since the singletons reference each other
LinkManager* linkManager = LinkManager::_createSingleton();
Q_UNUSED(linkManager);
Q_ASSERT(linkManager);
UASManagerInterface* uasManager = UASManager::instance();
// Needs LinkManager
UASManagerInterface* uasManager = UASManager::_createSingleton();
Q_UNUSED(uasManager);
Q_ASSERT(uasManager);
AutoPilotPluginManager* pluginManager = AutoPilotPluginManager::instance();
// Need UASManager
AutoPilotPluginManager* pluginManager = AutoPilotPluginManager::_createSingleton();
Q_UNUSED(pluginManager);
Q_ASSERT(pluginManager);
// Must be after UASManager since FactSystem connects to UASManager
FactSystem* factSystem = FactSystem::instance();
// Needs UASManager
FactSystem* factSystem = FactSystem::_createSingleton();
Q_UNUSED(factSystem);
Q_ASSERT(factSystem);
}
void QGCApplication::destroySingletonsForUnitTest(void)
void QGCApplication::_destroySingletons(void)
{
foreach(QGCSingleton* singleton, _singletons) {
Q_ASSERT(singleton);
singleton->deleteInstance();
}
if (MainWindow::instance()) {
delete MainWindow::instance();
}
_singletons.clear();
}
// Take down singletons in reverse order of creation
void QGCApplication::registerSingleton(QGCSingleton* singleton)
{
Q_ASSERT(singleton);
Q_ASSERT(!_singletons.contains(singleton));
_singletons.append(singleton);
FactSystem::_deleteSingleton();
AutoPilotPluginManager::_deleteSingleton();
UASManager::_deleteSingleton();
LinkManager::_deleteSingleton();
}
......@@ -84,16 +84,6 @@ public:
/// @brief Sets the flag to log all mavlink connections
void setPromptFlightDataSave(bool promptForSave);
/// @brief All global singletons must be registered such that QGCApplication::destorySingletonsForUnitTest
/// can work correctly.
void registerSingleton(QGCSingleton* singleton);
/// @brief Creates non-ui based singletons for unit testing
void createSingletonsForUnitTest(void) { _createSingletons(); }
/// @brief Destroys all singletons. Used by unit test code to reset global state.
void destroySingletonsForUnitTest(void);
/// @brief Returns truee if unit test are being run
bool runningUnitTests(void) { return _runningUnitTests; }
......@@ -114,6 +104,7 @@ public:
private:
void _createSingletons(void);
void _destroySingletons(void);
static const char* _settingsVersionKey; ///< Settings key which hold settings version
static const char* _deleteAllSettingsKey; ///< If this settings key is set on boot, all settings will be deleted
......@@ -124,9 +115,10 @@ private:
static const char* _savedFileMavlinkLogDirectoryName; ///< Name of mavlink log subdirectory
static const char* _savedFileParameterDirectoryName; ///< Name of parameter subdirectory
QList<QGCSingleton*> _singletons; ///< List of registered global singletons
bool _runningUnitTests; ///< true: running unit tests, false: normal app
/// Unit Test have access to creating and destroying singletons
friend class UnitTest;
};
/// @brief Returns the QGCApplication object singleton.
......
......@@ -29,10 +29,8 @@
#include "QGCSingleton.h"
#include "QGCApplication.h"
QGCSingleton::QGCSingleton(QObject* parent, bool registerSingleton) :
QGCSingleton::QGCSingleton(QObject* parent) :
QObject(parent)
{
if (registerSingleton) {
qgcApp()->registerSingleton(this);
}
}
......@@ -22,30 +22,137 @@
======================================================================*/
/// @file
/// @brief Base class for global singletons
///
/// @author Don Gagne <don@thegagnes.com>
#ifndef QGCSINGLETON_H
#define QGCSINGLETON_H
#include <QObject>
#include <QMutex>
/// @def DECLARE_QGC_SINGLETON
/// Include this macro in your Derived Class definition
/// @param className Derived Class name
/// @param interfaceName If your class is accessed through an interface specify that, if not specify Derived class name.
/// For example DECLARE_QGC_SINGLETON(UASManager, UASManagerInterface)
#define DECLARE_QGC_SINGLETON(className, interfaceName) \
public: \
static interfaceName* instance(void); \
static void setMockInstance(interfaceName* mock); \
private: \
static interfaceName* _createSingleton(void); \
static void _deleteSingleton(void); \
static interfaceName* _instance; \
static interfaceName* _mockInstance; \
static interfaceName* _realInstance; \
friend class QGCApplication; \
friend class UnitTest; \
/// @def IMPLEMENT_QGC_SINGLETON
/// Include this macro in your Derived Class implementation
/// @param className Derived Class name
/// @param interfaceName If your class is accessed through an interface specify that, if not specify Derived class name.
/// For example DECLARE_QGC_SINGLETON(UASManager, UASManagerInterface)
#define IMPLEMENT_QGC_SINGLETON(className, interfaceName) \
interfaceName* className::_instance = NULL; \
interfaceName* className::_mockInstance = NULL; \
interfaceName* className::_realInstance = NULL; \
\
interfaceName* className::_createSingleton(void) \
{ \
Q_ASSERT(_instance == NULL); \
_instance = new className(qgcApp()); \
return _instance; \
} \
\
void className::_deleteSingleton(void) \
{ \
if (className::_instance) { \
className* instance = qobject_cast<className*>(className::_instance); \
Q_ASSERT_X(instance != NULL, "QGCSingleton", "If you hit this assert you may have forgotten to clear a Mock instance"); \
className::_instance = NULL; \
delete instance; \
} \
} \
\
interfaceName* className::instance(void) \
{ \
Q_ASSERT_X(_instance, "QGCSingleton", "If you hit this, then you have run into a startup or shutdown sequence bug."); \
return _instance; \
} \
\
void className::setMockInstance(interfaceName* mock) \
{ \
if (mock) { \
Q_ASSERT(_instance); \
Q_ASSERT(!_realInstance); \
\
_realInstance = _instance; \
_instance = dynamic_cast<interfaceName*>(mock); \
Q_ASSERT(_instance); \
_mockInstance = mock; \
} else { \
Q_ASSERT(_instance); \
Q_ASSERT(_realInstance); \
\
_instance = _realInstance; \
_realInstance = NULL; \
_mockInstance = NULL; \
} \
}
class QGCApplication;
class UnitTest;
#include "QGCApplication.h"
/// This is the base class for all app global singletons
///
/// All global singletons are created/destroyed at boot time by QGCApplication::_createSingletons and destroyed by QGC::Application::_destroySingletons.
/// This is done in order to make sure they are all created on the main thread. As such no other code other than Unit Test
/// code has access to the constructor/destructor. QGCSingleton supports replacing singletons with a mock implementation.
/// In this case your object must derive from an interface which in turn derives from QGCSingleton. Youu can then use
/// the setMock method to add and remove you mock implementation. See UASManager example usage. In order to provide the
/// appropriate methods to make all this work you need to use the DECLARE_QGC_SINGLETON and IMPLEMENT_QGC_SINGLETON
/// macros as follows:
/// @code{.unparsed}
/// // Header file
///
/// class MySingleton : public QGCSingleton {
/// Q_OBJECT
///
/// DECLARE_QGC_SINGLETON(MySingleton, MySingleton)
///
/// ...
///
/// private:
/// // Constructor/Desctructor private since all access is through the singleton methods
/// MySingleton(QObject* parent == NULL);
/// ~MySingleton();
///
/// ...
/// }
///
/// // Code file
///
/// IMPLEMENT_QGC_SINGLETON(MySingleton, MySingleton)
///
/// MySingleton::MySingleton(QObject* parent) :
/// QGCSigleton(parent)
/// {
/// }
///
/// // Other class methods...
///
/// @endcode
/// The example above does not use an inteface so the second parameter to the macro is the class name as well.
class QGCSingleton : public QObject
{
Q_OBJECT
public:
/// @brief Contructor will register singleton to QGCApplication
protected:
/// Constructor is private since all creation is done through _createInstance
/// @param parent Parent object
/// @param registerSingleton true: register with QGCApplication, false: do not register (only used for Mock implementations)
QGCSingleton(QObject* parent = NULL, bool registerSingleton = true);
/// @brief Implementation should delete the singleton such that next call to instance
/// will create a new singleton.
virtual void deleteInstance(void) = 0;
QGCSingleton(QObject* parent = NULL);
};
#endif
......@@ -38,41 +38,18 @@ This file is part of the QGROUNDCONTROL project
#include "QGCMessageBox.h"
#include "QGCApplication.h"
LinkManager* LinkManager::_instance = NULL;
LinkManager* LinkManager::instance(void)
{
if(_instance == 0) {
new LinkManager(qgcApp());
Q_CHECK_PTR(_instance);
}
Q_ASSERT(_instance);
return _instance;
}
void LinkManager::deleteInstance(void)
{
_instance = NULL;
delete this;
}
IMPLEMENT_QGC_SINGLETON(LinkManager, LinkManager)
/**
* @brief Private singleton constructor
*
* This class implements the singleton design pattern and has therefore only a private constructor.
**/
LinkManager::LinkManager(QObject* parent, bool registerSingleton) :
QGCSingleton(parent, registerSingleton),
LinkManager::LinkManager(QObject* parent) :
QGCSingleton(parent),
_connectionsSuspended(false),
_mavlink(NULL)
{
if (registerSingleton) {
Q_ASSERT(_instance == NULL);
_instance = this;
}
_mavlink = new MAVLinkProtocol(this);
Q_CHECK_PTR(_mavlink);
}
......
......@@ -49,14 +49,13 @@ class LinkManagerTest;
class LinkManager : public QGCSingleton
{
Q_OBJECT
public:
/// @brief Returns the LinkManager singleton
static LinkManager* instance(void);
virtual void deleteInstance(void);
~LinkManager();
DECLARE_QGC_SINGLETON(LinkManager, LinkManager)
/// Unit Test has access to private constructor/destructor
friend class LinkManagerTest;
public:
/// Returns list of all links
const QList<LinkInterface*> getLinks();
......@@ -99,19 +98,15 @@ signals:
private:
/// All access to LinkManager is through LinkManager::instance
LinkManager(QObject* parent = NULL, bool registerSingleton = true);
/// LinkManager unit test is allowed to new LinkManager objects
friend class LinkManagerTest;
LinkManager(QObject* parent = NULL);
~LinkManager();
bool _connectionsSuspendedMsg(void);
static LinkManager* _instance; /// LinkManager singleton
QList<LinkInterface*> _links; ///< List of available links
QMutex _linkListMutex; ///< Mutex for thread safe access to _links list
bool _connectionsSuspended; ///< true: all new connections should not be allowed
QString _connectionsSuspendedReason; ///< User visible reason for suspension
......
......@@ -45,7 +45,7 @@ void LinkManagerTest::init(void)
Q_ASSERT(_linkMgr == NULL);
Q_ASSERT(_multiSpy == NULL);
_linkMgr = new LinkManager(NULL /* no parent */, false /* don't register singleton */);
_linkMgr = new LinkManager(NULL /* no parent */);
Q_CHECK_PTR(_linkMgr);
_rgSignals[newLinkSignalIndex] = SIGNAL(newLink(LinkInterface*));
......@@ -57,8 +57,6 @@ void LinkManagerTest::init(void)
void LinkManagerTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_linkMgr);
Q_ASSERT(_multiSpy);
......@@ -67,18 +65,11 @@ void LinkManagerTest::cleanup(void)
_linkMgr = NULL;
_multiSpy = NULL;
}
void LinkManagerTest::_instance_test(void)
{
LinkManager *linkManager = new LinkManager(NULL /* no parent */, false /* don't register singleton */);
// If the flag to not register singleton is not working this will cause QGCApplication to crash on
// desctrucion since it will try to de-reference a deleted singleton.
delete linkManager;
UnitTest::cleanup();
}
void LinkManagerTest::_add_test(void)
{
Q_ASSERT(_linkMgr);
......
......@@ -44,7 +44,6 @@ private slots:
void init(void);
void cleanup(void);
void _instance_test(void);
void _add_test(void);
void _delete_test(void);
void _addSignals_test(void);
......
......@@ -59,12 +59,12 @@ void MavlinkLogTest::init(void)
void MavlinkLogTest::cleanup(void)
{
UnitTest::cleanup();
// Make sure no left over logs in temp directory
QDir tmpDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
QStringList logFiles(tmpDir.entryList(QStringList(QString("*.%1").arg(_logFileExtension)), QDir::Files));
QCOMPARE(logFiles.count(), 0);
UnitTest::cleanup();
}
void MavlinkLogTest::_createTempLogFile(bool zeroLength)
......
......@@ -24,7 +24,7 @@
#include "MockUASManager.h"
MockUASManager::MockUASManager(void) :
UASManagerInterface(NULL, false /* do not register singleton with QGCApplication */),
UASManagerInterface(NULL),
_mockUAS(NULL)
{
......
......@@ -156,7 +156,7 @@ void PX4RCCalibrationTest::init(void)
_mockUASManager = new MockUASManager();
Q_ASSERT(_mockUASManager);
UASManager::setMockUASManager(_mockUASManager);
UASManager::setMockInstance(_mockUASManager);
_mockUAS = new MockUAS();
Q_CHECK_PTR(_mockUAS);
......@@ -201,19 +201,18 @@ void PX4RCCalibrationTest::init(void)
void PX4RCCalibrationTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_calWidget);
delete _calWidget;
Q_ASSERT(_mockUAS);
delete _mockUAS;
UASManager::setMockUASManager(NULL);
UASManager::setMockInstance(NULL);
Q_ASSERT(_mockUASManager);
delete _mockUASManager;
UnitTest::cleanup();
}
/// @brief Test for correct behavior in determining minimum numbers of channels for flight.
......
......@@ -88,8 +88,6 @@ void QGCUASFileManagerUnitTest::init(void)
// Called after every test case
void QGCUASFileManagerUnitTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_multiSpy);
Q_ASSERT(_fileManager);
......@@ -98,6 +96,8 @@ void QGCUASFileManagerUnitTest::cleanup(void)
_fileManager = NULL;
_multiSpy = NULL;
UnitTest::cleanup();
}
/// @brief Connected to QGCUASFileManager listEntry signal in order to catch list entries
......
......@@ -68,8 +68,6 @@ void TCPLinkUnitTest::init(void)
// Called after every test
void TCPLinkUnitTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_multiSpy);
Q_ASSERT(_link);
......@@ -78,6 +76,8 @@ void TCPLinkUnitTest::cleanup(void)
_multiSpy = NULL;
_link = NULL;
UnitTest::cleanup();
}
void TCPLinkUnitTest::_properties_test(void)
......
......@@ -20,13 +20,13 @@ void UASUnitTest::init()
//this function is called after every test
void UASUnitTest::cleanup()
{
UnitTest::cleanup();
delete _uas;
_uas = NULL;
delete _mavlink;
_mavlink = NULL;
UnitTest::cleanup();
}
void UASUnitTest::getUASID_test()
......
......@@ -114,8 +114,8 @@ void UnitTest::init(void)
_expectMissedMessageBox = false;
// Each test gets a clean global state
qgcApp()->destroySingletonsForUnitTest();
qgcApp()->createSingletonsForUnitTest();
qgcApp()->_destroySingletons();
qgcApp()->_createSingletons();
MAVLinkProtocol::deleteTempLogFiles();
}
......@@ -136,7 +136,7 @@ void UnitTest::cleanup(void)
}
QCOMPARE(_missedFileDialogCount, 0);
qgcApp()->destroySingletonsForUnitTest();
qgcApp()->_destroySingletons();
}
void UnitTest::setExpectedMessageBox(QMessageBox::StandardButton response)
......
......@@ -24,35 +24,29 @@
#define MEAN_EARTH_DIAMETER 12756274.0
#define UMR 0.017453292519943295769236907684886
UASManager* UASManager::_instance = NULL;
UASManagerInterface* UASManager::_mockUASManager = NULL;
IMPLEMENT_QGC_SINGLETON(UASManager, UASManagerInterface)
void UASManager::setMockUASManager(UASManagerInterface* mockUASManager)
UASManager::UASManager(QObject* parent) :
UASManagerInterface(parent),
activeUAS(NULL),
offlineUASWaypointManager(NULL),
homeLat(47.3769),
homeLon(8.549444),
homeAlt(470.0),
homeFrame(MAV_FRAME_GLOBAL)
{
_mockUASManager = mockUASManager;
loadSettings();
setLocalNEDSafetyBorders(1, -1, 0, -1, 1, -1);
}
UASManagerInterface* UASManager::instance()
UASManager::~UASManager()
{
if (_mockUASManager) {
return _mockUASManager;
}
if(_instance == NULL) {
_instance = new UASManager(qgcApp());
Q_CHECK_PTR(_instance);
storeSettings();
// Delete all systems
foreach (UASInterface* mav, systems) {
// deleteLater so it ends up on correct thread
mav->deleteLater();
}
Q_ASSERT(_instance);
return _instance;
}