From dc53be520b5233cffda1a05abc544c8f4221d99b Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Fri, 20 Jul 2007 01:50:03 +0200 Subject: [PATCH] Introduced a new notification system for Settings changes. The new system is still based on the QMetaObject facility, but does not involve assigning priorities to Settings observers. Instead, a very rudimental dependency enforcing mechanism is employed: at connection time (i.e. when calling the onChange function) one can specify an optional class name as a dependency. Whenever an update is triggered, the systems ensures that dependencies are honored, i.e. an object depending on a class "X" will be notified after all observing objects of class "X". The assumption here is that controlling dependency via types is sufficient for our purposes. As for the API, we now have to "onChange" methods, one that takes a dependency (as a C-style string representing a class name) and one that does not. The slot should be specified as a C-style string with just the method name (no parentheses, no SLOT macro). --- src/common.h | 9 +++-- src/graphicalgame.cpp | 2 +- src/graphicalsystem.cpp | 2 +- src/loader/theme.cpp | 2 +- src/mastersettings.cpp | 89 +++++++++++++++++++++++++++++++++---------------- src/mastersettings.h | 44 ++++++++++++++++++------ 6 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/common.h b/src/common.h index 3b88846..c01c196 100644 --- a/src/common.h +++ b/src/common.h @@ -28,9 +28,6 @@ template class Grid; template class PointerGrid; //typedef PointerGrid PieceGrid; -#define FORALL(i, collection, type) \ - for (type::iterator i = (collection).begin(); i != (collection).end(); ++i) - std::ostream &operator <<(std::ostream &os, const QString& s); @@ -52,8 +49,10 @@ inline void TRAP() { #define M_PI 3.1415926 #endif -#define ERROR(x) (std::cout << "Error: " << x << std::endl \ - << " in " << __PRETTY_FUNCTION__ << ", line " << __LINE__ << " of " << __FILE__ <setNotifier( static_cast(this) ); m_movelist->show(); } - settings.onChange(this, SLOT(settingsChanged())); + settings.onChange(this, "settingsChanged", "Loader::Theme"); settingsChanged(); } diff --git a/src/graphicalsystem.cpp b/src/graphicalsystem.cpp index 3e7e040..0fd2ebf 100644 --- a/src/graphicalsystem.cpp +++ b/src/graphicalsystem.cpp @@ -46,7 +46,7 @@ GraphicalSystem::GraphicalSystem(ChessTable* view, m_animator = m_variant->createAnimator(this); - settings.onChange( this, SLOT(settingsChanged())); + settings.onChange(this, "settingsChanged", "Loader::Theme"); settingsChanged(); if (startingPosition) diff --git a/src/loader/theme.cpp b/src/loader/theme.cpp index e170848..9ee8ad5 100644 --- a/src/loader/theme.cpp +++ b/src/loader/theme.cpp @@ -41,7 +41,7 @@ Theme::Theme(const QString& lua_file) if(m_lua_loader.error()) ERROR("Script load error: " << std::endl << m_lua_loader.errorString()); - settings.onChange(this, SLOT(onSettingsChanged()), -10); + settings.onChange(this, "onSettingsChanged"); onSettingsChanged(); } diff --git a/src/mastersettings.cpp b/src/mastersettings.cpp index 0700ff2..e1d2dda 100644 --- a/src/mastersettings.cpp +++ b/src/mastersettings.cpp @@ -55,10 +55,56 @@ MasterSettings::~MasterSettings() { sync(); } -void MasterSettings::onChange(QObject* obj, const char* slot, int prority) { - std::cout << "register " << obj << " " << slot << std::endl; - m_slots[prority].insert( std::pair(obj, slot) ); - connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(obj_destroyed(QObject*))); +void MasterSettings::setupObserver(Observer& observer) { + std::cout << "adding observer: " << observer.object->metaObject()->className() + << "::" << wrap_cptr(observer.method) + << " (dep. " << wrap_cptr(observer.dependency) << ")" << std::endl; + + connect(observer.object, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(QObject*))); +} + +void MasterSettings::onChange(QObject* obj, const char* method) { + m_observers.push_front(Observer(obj, method, 0)); + setupObserver(m_observers.front()); +} + +void MasterSettings::onChange(QObject* obj, const char* method, const char* dependency) { + if (dependency) { + // go backwards through all existing observers, searching for something + // on which we depend. + for (ObserverList::reverse_iterator it = m_observers.rbegin(); + it != m_observers.rend(); ++it) { + const char* observer_class = it->object->metaObject()->className(); + if (observer_class && strcmp(observer_class, dependency) == 0) { + // we hit a dependency wall: we can't be notified + // before *it, so add the new Observer just here + ObserverList::iterator us = m_observers.insert(it.base(), Observer(obj, method, dependency)); + setupObserver(*us); + + // now check for a cyclic dependency + const char* this_class = obj->metaObject()->className(); + for (ObserverList::iterator it2 = m_observers.begin(); it2 != us; ++it2) { + if (it2->dependency && strcmp(it2->dependency, this_class) == 0) { + // something which is notified before has us a dependency + // this means that there is a cyclic dependency it2 -> us -> it. + WARNING("Removing a cyclic dependency: " << + it2->object->metaObject()->className() << " -> " << + this_class << " -> " << + observer_class); + + // remove the cycle + it2->dependency = 0; + } + } + + // done + return; + } + } + } + + // no dependency + onChange(obj, method); } void MasterSettings::sync() { @@ -73,36 +119,21 @@ void MasterSettings::sync() { } -void MasterSettings::obj_destroyed(QObject* obj) { - for (SlotMap::iterator it = m_slots.begin(); it != m_slots.end(); /*++it*/ ) { - SlotSet::iterator j = it->second.lower_bound( std::pair(obj, NULL) ); - while(j->first == obj) - it->second.erase(j++); - - if(it->second.empty()) - m_slots.erase(it++); - else +void MasterSettings::objectDestroyed(QObject* obj) { + for (ObserverList::iterator it = m_observers.begin(); + it != m_observers.end();) { + if (it->object == obj) { + it = m_observers.erase(it); + } + else { ++it; + } } } void MasterSettings::changed() { - for (SlotMap::iterator it = m_slots.begin(); it != m_slots.end(); ++it ) - for (SlotSet::iterator j = it->second.begin(); j != it->second.end(); ++j ) { - - //yes, moc is shit. - char* mystr = (char*)alloca(strlen(j->second)+1); - if(isdigit(j->second[0])) - strcpy(mystr, j->second+1); - else - strcpy(mystr, j->second); - - if(char* par = index(mystr, '(')) - *par = '\0'; - if(char* par = index(mystr, ' ')) - *par = '\0'; - - bool r = j->first->metaObject()->invokeMethod( j->first, mystr, Qt::DirectConnection); + foreach (Observer& observer, m_observers) { + observer.object->metaObject()->invokeMethod(observer.object, observer.method, Qt::DirectConnection); } sync(); } diff --git a/src/mastersettings.h b/src/mastersettings.h index 1ceb69b..8f12fde 100644 --- a/src/mastersettings.h +++ b/src/mastersettings.h @@ -13,8 +13,7 @@ #include #include -#include -#include +#include #include #include "settings.h" #include "foreach.hpp" @@ -24,17 +23,25 @@ class MasterSettings : public QObject , public Settings { Q_OBJECT + struct Observer { + QObject* object; + const char* dependency; + const char* method; + + Observer(QObject* obj, const char* meth, const char* dep) + : object(obj) + , dependency(dep) + , method(meth) { } + }; + typedef std::list ObserverList; -private: - typedef std::set > SlotSet; - typedef std::map SlotMap; - - SlotMap m_slots; + ObserverList m_observers; QString m_filename; mutable QDomDocument m_doc; - + + void setupObserver(Observer&); private slots: - void obj_destroyed(QObject* o); + void objectDestroyed(QObject* o); protected: virtual QDomElement node() const; @@ -43,7 +50,24 @@ public: MasterSettings(const QString& file); ~MasterSettings(); - void onChange(QObject* receiver, const char* slot, int priority = 0); + /** + * Set up an observer to be notified whenever settings change. + * \param observer The object to be notified. + * \param method The callback method for the notification, specified as a C string. + * \note @a method should be just the method name (no parentheses, no SLOT macro). + */ + void onChange(QObject* observer, const char* method); + + /** + * Set up an observer to be notified whenever settings change. + * \param observer The object to be notified. + * \param method The callback method for the notification, specified as a C string. + * \param dependency A C string representing a type (inheriting from QObject). Objects + * of this type will be notified before @a observer. + * \note @a method should be just the method name (no parentheses, no SLOT macro). + */ + void onChange(QObject* observer, const char* method, const char* dependency); + void changed(); void sync(); }; -- 2.11.4.GIT