Thoughts about ChangeBroadcaster


#1

I have a DocumentClass which is a ChangeBroadcaster.

My ViewComponent is added as a ChangeListener to the DocumentClass.

Whenever ViewComponent->changeListenerCallback is called, it makes callbacks to DocumentClass to update its own content.

–> When my ViewComponent is in destruction-phase, the ChangeListener should remove itself as a listener for all connected Broadcasters automatically, expect the ChangeBroadcaster is deleted before (which should be checked).
Also no pending callbacks should be called while or after destruction phase.

But what happens if my DocumentClass is deleted, and we have pending Messages.
We have to care that all pendingMessages should have proceeded, at the beginning of the destruction-phase of a class which inherits the ChangeBroadCaster.

class DocumentClass : public ChangeBroadcaster { ~DocumentClass() { dispatchPendingMessages(); // do other destruction stuff } }

I’m right or I’m thinking a little bit overcomplicated?


#2

to explain this:

class ViewComponent;

class DocumentClass : public ChangeBroadcaster
{
public:
	DocumentClass();
	~DocumentClass();

	void setData(int newData);

	int getData();

	ViewComponent* createDocumentView();

	bool isInDestruction();

public:
	int data;
	bool inDestruction;
};

class ViewComponent : public Component , public ChangeListener
{
public:
	ViewComponent(DocumentClass* sourceDocument_);

	~ViewComponent();

	void changeListenerCallback(ChangeBroadcaster* source);

	void paint(Graphics& g);
private:
	int dataCopy;
	DocumentClass* sourceDocument;
};

DocumentClass::DocumentClass() : data(123456), inDestruction(false)
{

}


DocumentClass::~DocumentClass()
{
	inDestruction=true;
	sendChangeMessage();
	dispatchPendingMessages();
}

void DocumentClass::setData( int newData )
{
	if (data!=newData)
	{
		data=newData;
		sendChangeMessage();
	}
}

int DocumentClass::getData()
{
	return data;
}

ViewComponent* DocumentClass::createDocumentView()
{
	ViewComponent* view=new ViewComponent(this);
	addChangeListener(view);
	sendChangeMessage();
	return view;
}

bool DocumentClass::isInDestruction()
{
	return inDestruction;
}

ViewComponent::ViewComponent( DocumentClass* sourceDocument_ ) :
sourceDocument(sourceDocument_)
{
	dataCopy=sourceDocument->getData();
}

ViewComponent::~ViewComponent()
{
	if (sourceDocument!=0)
	{
		sourceDocument->removeChangeListener(this);
	}
}

void ViewComponent::changeListenerCallback( ChangeBroadcaster* source )
{
	if (sourceDocument->isInDestruction()==false)
	{
		dataCopy=sourceDocument->data;
		repaint();
	} else
	{
		sourceDocument=0;
	}
}

void ViewComponent::paint( Graphics& g )
{
	// display dataCopy
	g.drawText(String(dataCopy),0,0,getWidth(),getHeight(),Justification::centred,false);
}

#3

or is there an easier way?


#4

Not sure I understand your problem?

As long as your ChangeListeners unregister themselves in their destructors, there’s no way and dangling pointers can be called… Is that what you’re saying?


#5

its not a problem, its more a “what its the best way to do this with JUCE”

The DocumentClass creates a ViewComponent which displays the DocumentClass content.
When the ViewComponent is deleted first, it can unregister themself (because it stores the Broadcasters pointer)

Maybe you can add something to the listener which does this automatically.

The second thing is, what happens when the DocumentClass will be deleted before the ViewComponent (because when ViewComponent unregisters itself, it needs to know if the DocumentClass still exits)

In my example, the DocumentClass sets a flag (inDestruction=true), sends a sends a changeMessage and dispatchPendingMessages(), so that the viewComponent can recognize thats its datasource will be deleted and can react.

Maybe you can add a second Callback to the ChangeListener which informs the ChangeListener that a broadcaster is going to be deleted.

Or is there a more elegant way? (or i need something like a non-Component Safe-Pointer for Broadcasters).

[code]DocumentClass::~DocumentClass()
{
inDestruction=true;
sendChangeMessage();
dispatchPendingMessages();
}

void ViewComponent::changeListenerCallback( ChangeBroadcaster* source )
{
if (sourceDocument->isInDestruction()==false)
{
dataCopy=sourceDocument->data;
repaint();
} else
{
sourceDocument=0;
}
}[/code]


#6

If your code needs to know when something gets deleted, then you should write a class for it, e.g. “MyDocumentDeletionListener”, which would be trivial to implement yourself by giving your document a ListenerList. It’s not something that has any place in changelistener - a changelistener doesn’t care whether about whether its sources have been deleted or not.

But personally, I’d just make the document object reference-counted, so it won’t be deleted until you close all the views.


#7

thanks, i made my own DeletionBroadcaster/Listener and a SafePointer-Template, seems to work fine.

In this case i cannot use ref. counted objects, because the class is a rich object (which file handles etc., memory) and its livinig time should not be bounded on components - living time.

Here is my DeletionBroadcaster/Listener and DeletionSafePointer (maybe a nice addition for JUCE):

[code]class DeletionBroadcaster;
class DeletionListener;

class DeletionListener
{
public:
virtual ~DeletionListener() {}

virtual void deletionListenerCallback (DeletionBroadcaster* source) = 0;

};

class DeletionBroadcaster
{
public:

DeletionBroadcaster();;

~DeletionBroadcaster();;

void addDeletionListener(DeletionListener* listener);;

void removeDeletionListener(DeletionListener* listener);

void sendDeletionMessage();;

private:
ListenerList deletionListeners;
bool deletionMessageSended;
};

class DeletionChecker : public DeletionListener
{
public:
DeletionChecker(DeletionBroadcaster* source_);;

~DeletionChecker();

bool isNotDeleted();;

void deletionListenerCallback(DeletionBroadcaster* source);;

private:
DeletionBroadcaster* broadcasterSource;

};

template
class DeletionSafePointer
{
public:

inline DeletionSafePointer (ObjectType* const objectToCare) throw()
    : object (objectToCare) , deletionChecker(objectToCare)
{
}

inline ~DeletionSafePointer()                              { }

inline operator ObjectType*()  throw()                                     
{ 
	if (deletionChecker.isNotDeleted())
	{
		return object;
	} else
	{
		return 0;
	}
}

inline ObjectType& operator*()  throw()                                    
{ 
	if (deletionChecker.isNotDeleted())
	{
		return *object;
	} else
	{
		return 0;
	}
}

inline ObjectType* operator->() throw()                                   
{
	if (deletionChecker.isNotDeleted())
	{
		return object;
	} else
	{
		jassertfalse;
		return 0;
	}
}

private:

ObjectType* object;

DeletionChecker deletionChecker;

};

template
bool operator== (const DeletionSafePointer& pointer1, ObjectType* const pointer2) throw()
{
return static_cast <ObjectType*> (pointer1) == pointer2;
}

template
bool operator!= (const DeletionSafePointer& pointer1, ObjectType* const pointer2) throw()
{
return static_cast <ObjectType*> (pointer1) != pointer2;
}
[/code]

[code]
DeletionChecker::DeletionChecker( DeletionBroadcaster* source_ ) : broadcasterSource(source_)
{
broadcasterSource->addDeletionListener(this);
}

DeletionChecker::~DeletionChecker()
{
if (isNotDeleted())
{
broadcasterSource->removeDeletionListener(this);
}
}

bool DeletionChecker::isNotDeleted()
{

return (broadcasterSource!=0);

}

void DeletionChecker::deletionListenerCallback( DeletionBroadcaster* source_ )
{

broadcasterSource=0;

}

DeletionBroadcaster::DeletionBroadcaster() : deletionMessageSended(false)
{

}

DeletionBroadcaster::~DeletionBroadcaster()
{
if (deletionMessageSended==false)
{
jassertfalse;
// Please call sendDeletionMessage in the destructor !!!
// ~myClass()
// {
// sendDeletionMessage();
// do other destruction
// };
sendDeletionMessage();
};
}

void DeletionBroadcaster::addDeletionListener( DeletionListener* listener )
{
deletionListeners.add(listener);
}

void DeletionBroadcaster::removeDeletionListener( DeletionListener* listener )
{
deletionListeners.remove(listener);
}

void DeletionBroadcaster::sendDeletionMessage()
{
deletionListeners.call(&DeletionListener::deletionListenerCallback,this);
deletionMessageSended=true;
}[/code]