cc_
11-06-2009, 01:11 AM
I needed something general so I could get informed when a control changed. So I implemented a simple observer/subject thing and made IControl inherit from that, so observers can just register with the control and get called when the control is marked dirty.
I'll post up an example of using it when I get a chance, but the basic idea is to make a class that contains the control you want to observe and make that class inherit from IObserverInterface:
#include "IControl.h"
class WControl : public IObserverInterface
{
...
protected:
IControl * mpControl;
...
Then in WControl make the control and call RegisterObserver on it:
mpControl = new IwhateverControl...
mpControl->RegisterObserver(this);
Finally your class should implement an update function - this is what gets called when the control is dirty:
void WControl::Update() {
// check the state of the control and do stuff here
}
Here is ISubject.h . It is a little different from the one I posted on the other thread, because I found some problems with that. This one has been tested pretty well.
#ifndef _ISUBJECT_
#define _ISUBJECT_
#include "../ptrlist.h"
/*
* This is based on the Observer design pattern, but because I need
* to make sure all the NotifyObservers calls happen on the correct
* thread there is SubjectDirty() method which the subject should call
* whenever it changes (on any thread). The when you're in the
* correct thread call NotifyObservers() for all the subjects and
* the call will only be done for those that are dirty (also unsets dirty).
*
* Example: if you want to be able to watch an IControl, then
* IControl should be a public ISubject and should call SubjectDirty()
* in its SetDirty() call* (which can be called from either the GUI
* thread [if the control changed from the GUI] or another thread
* in the case of parameter automation.
* Then when you are in the GUI thread for sure you should go through
* all the controls and call NotifyObservers() on each. A convinient
* place to do this is IGraphics::IsDirty().
* An observer should be a IObserverInterface and call
* Register to register itself with the IControl.
* The update has no parameters, so the observer needs to
* keep track of who it's watching (if this is a problem the update
* method could be changed to deliver a pointer to the subject).
*
* If there are no observers the overhead should just be one extra compare
* (mpObs is NULL). Observers are stored in a WDL_PtrList.
*
* * note: we can't use the IControl's dirty flag as the dirty flag
* for the subject, because a call might cause a subject we have
* already checked to go dirty and then that subject could be drawn
* (clearing the IControl dirty flag) and the update would never be called.
*/
// The Abstract Observer Interface
class IObserverInterface
{
public:
virtual void Update() = 0;
virtual ~IObserverInterface() {}
};
// The Subject
class ISubject
{
public:
ISubject() : mpObs(NULL), mDirty(false) {}
virtual ~ISubject() { delete mpObs; }
void RegisterObserver(IObserverInterface* ob) {
// wdl pointer list has 32 byte granularity as we don't expect many observers
if ( ! mpObs) mpObs = new WDL_PtrList<IObserverInterface>(32);
if (-1 == mpObs->Find(ob))
mpObs->Add(ob);
}
void RemoveObserver(IObserverInterface* ob) {
// note: it is safe to call remove even if you didn't register
// find will return -1 and delete will do nothing
if (mpObs) {
mpObs->Delete(mpObs->Find(ob));
}
}
void inline NotifyObservers() {
if (mpObs && mDirty) {
mDirty = false;
int n = mpObs->GetSize();
for (int i = 0; i < n; ++i) {
IObserverInterface* ob = mpObs->Get(i);
ob->Update();
}
}
}
void inline SubjectDirty() {
mDirty=true;
}
private:
WDL_PtrList<IObserverInterface> *mpObs;
bool mDirty;
};
I found I couldn't really call remove because the IControls get destroyed before my objects when the sequencer exits.
And the changes to IControl.h :
#include "ISubject.h"
class IControl : public ISubject
In IControl.cpp in SetDirty after mDirty = true;
SubjectDirty();
In IGraphics.cpp in IsDirty after IControl* pControl = *ppControl;
pControl->NotifyObservers();
I'll post up an example of using it when I get a chance, but the basic idea is to make a class that contains the control you want to observe and make that class inherit from IObserverInterface:
#include "IControl.h"
class WControl : public IObserverInterface
{
...
protected:
IControl * mpControl;
...
Then in WControl make the control and call RegisterObserver on it:
mpControl = new IwhateverControl...
mpControl->RegisterObserver(this);
Finally your class should implement an update function - this is what gets called when the control is dirty:
void WControl::Update() {
// check the state of the control and do stuff here
}
Here is ISubject.h . It is a little different from the one I posted on the other thread, because I found some problems with that. This one has been tested pretty well.
#ifndef _ISUBJECT_
#define _ISUBJECT_
#include "../ptrlist.h"
/*
* This is based on the Observer design pattern, but because I need
* to make sure all the NotifyObservers calls happen on the correct
* thread there is SubjectDirty() method which the subject should call
* whenever it changes (on any thread). The when you're in the
* correct thread call NotifyObservers() for all the subjects and
* the call will only be done for those that are dirty (also unsets dirty).
*
* Example: if you want to be able to watch an IControl, then
* IControl should be a public ISubject and should call SubjectDirty()
* in its SetDirty() call* (which can be called from either the GUI
* thread [if the control changed from the GUI] or another thread
* in the case of parameter automation.
* Then when you are in the GUI thread for sure you should go through
* all the controls and call NotifyObservers() on each. A convinient
* place to do this is IGraphics::IsDirty().
* An observer should be a IObserverInterface and call
* Register to register itself with the IControl.
* The update has no parameters, so the observer needs to
* keep track of who it's watching (if this is a problem the update
* method could be changed to deliver a pointer to the subject).
*
* If there are no observers the overhead should just be one extra compare
* (mpObs is NULL). Observers are stored in a WDL_PtrList.
*
* * note: we can't use the IControl's dirty flag as the dirty flag
* for the subject, because a call might cause a subject we have
* already checked to go dirty and then that subject could be drawn
* (clearing the IControl dirty flag) and the update would never be called.
*/
// The Abstract Observer Interface
class IObserverInterface
{
public:
virtual void Update() = 0;
virtual ~IObserverInterface() {}
};
// The Subject
class ISubject
{
public:
ISubject() : mpObs(NULL), mDirty(false) {}
virtual ~ISubject() { delete mpObs; }
void RegisterObserver(IObserverInterface* ob) {
// wdl pointer list has 32 byte granularity as we don't expect many observers
if ( ! mpObs) mpObs = new WDL_PtrList<IObserverInterface>(32);
if (-1 == mpObs->Find(ob))
mpObs->Add(ob);
}
void RemoveObserver(IObserverInterface* ob) {
// note: it is safe to call remove even if you didn't register
// find will return -1 and delete will do nothing
if (mpObs) {
mpObs->Delete(mpObs->Find(ob));
}
}
void inline NotifyObservers() {
if (mpObs && mDirty) {
mDirty = false;
int n = mpObs->GetSize();
for (int i = 0; i < n; ++i) {
IObserverInterface* ob = mpObs->Get(i);
ob->Update();
}
}
}
void inline SubjectDirty() {
mDirty=true;
}
private:
WDL_PtrList<IObserverInterface> *mpObs;
bool mDirty;
};
I found I couldn't really call remove because the IControls get destroyed before my objects when the sequencer exits.
And the changes to IControl.h :
#include "ISubject.h"
class IControl : public ISubject
In IControl.cpp in SetDirty after mDirty = true;
SubjectDirty();
In IGraphics.cpp in IsDirty after IControl* pControl = *ppControl;
pControl->NotifyObservers();