/* * Observer.cpp * * Created on: Jan 19, 2010 * Author: crueger */ #include "Observer.hpp" #include #include using namespace std; /****************** Static stuff for the observer mechanism ************/ // All infrastructure for the observer-pattern is bundled at a central place // this is more efficient if many objects can be observed (inherit from observable) // but only few are actually coupled with observers. E.g. TMV has over 500.000 Atoms, // which might become observable. Handling Observerable infrastructure in each of // these would use memory for each atom. By handling Observer-infrastructure // here we only need memory for objects that actually are observed. // See [Gamma et al, 1995] p. 297 map Observable::depth; map*> Observable::callTable; set Observable::busyObservables; // The two functions start_observer_internal and finish_observer_internal // have to be used together at all time. Never use these functions directly // START_OBSERVER and FINISH_OBSERVER also construct a bogus while(0) loop // thus producing compiler-errors whenever only one is used void Observable::start_observer_internal(Observable *publisher){ // increase the count for this observable by one // if no entry for this observable is found, an new one is created // by the STL and initialized to 0 (see STL documentation) depth[publisher]++; } void Observable::finish_observer_internal(Observable *publisher){ // decrease the count for this observable // if zero is reached all observed blocks are done and we can // start to notify our observers if(--(depth[publisher])){} else{ publisher->notifyAll(); // this item is done, so we don't have to keep the count with us // save some memory by erasing it depth.erase(publisher); } } /************* Notification mechanism for observables **************/ void Observable::notifyAll() { // we are busy notifying others right now // add ourselves to the list of busy subjects to enable circle detection busyObservables.insert(this); // see if anyone has signed up for observation // and call all observers if(callTable.count(this)) { // elements are stored sorted by keys in the multimap // so iterating over it gives us a the callees sorted by // the priorities callees_t *callees = callTable[this]; callees_t::iterator iter; for(iter=callees->begin();iter!=callees->end();iter++){ (*iter).second->update(this); } } // done with notification, we can leave the set of busy subjects busyObservables.erase(this); } // this handles passing on updates from sub-Observables void Observable::update(Observable *publisher) { // circle detection if(busyObservables.find(this)!=busyObservables.end()) { // somehow a circle was introduced... we were busy notifying our // observers, but still we are called by one of our sub-Observables // we cannot be sure observation will still work at this point cerr << "Circle detected in observation-graph." << endl; cerr << "Observation-graph always needs to be a DAG to work correctly!" << endl; cerr << "Please check your observation code and fix this!" << endl; return; } else { // see if we are in the process of changing ourselves // if we are changing ourselves at the same time our sub-observables change // we do not need to publish all the changes at each time we are called if(depth.find(this)==depth.end()) { notifyAll(); } } } // methods to sign-on and off void Observable::signOn(Observer *target,int priority) { assert(priority>=-20 && priority<=+20 && "Priority out of range [-20:+20]"); bool res = false; callees_t *callees = 0; if(callTable.count(this)){ callees = callTable[this]; } else { callees = new multimap; callTable.insert(pair(this,callees)); } callees_t::iterator iter; for(iter=callees->begin();iter!=callees->end();iter++){ res |= ((*iter).second == target); } if(!res) callees->insert(pair(priority,target)); } void Observable::signOff(Observer *target) { assert(callTable.count(this) && "SignOff called for an Observable without Observers."); callees_t *callees = callTable[this]; callees_t::iterator iter; for(iter=callees->begin();iter!=callees->end();iter++) { if((*iter).second == target) callees->erase(iter); } if(callees->empty()){ callTable.erase(this); delete callees; } } // when an sub-observerable dies we usually don't need to do anything void Observable::subjectKilled(Observable *publisher){ } Observable::Observable() {} // when an observable is deleted, we let all our observers know Observable::~Observable() { if(callTable.count(this)) { // delete all entries for this observable callees_t *callees = callTable[this]; callees_t::iterator iter; for(iter=callees->begin();iter!=callees->end();iter++){ (*iter).second->subjectKilled(this); } callTable.erase(this); delete callees; } } Observer::Observer() {} Observer::~Observer() {}