Source: ../../../tse3/src/tse3/Notifier.h
|
|
|
|
/*
* @(#)Notifier.h 3.00 13 July 2000
*
* Copyright (c) 2000 Pete Goodliffe (pete@cthree.org)
*
* This file is part of TSE3 - the Trax Sequencer Engine version 3.00.
*
* This library is modifiable/redistributable under the terms of the GNU
* General Public License.
*
* You should have received a copy of the GNU General Public License along
* with this program; see the file COPYING. If not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#ifndef TSE3_NOTIFIER_H
#define TSE3_NOTIFIER_H
#include
/******************************************************************************
* This file contains the implementation of the TSE3 Notifier framework.
* It is the TSE3 implementation of the observer design pattern (GoF book).
*
* The public API (the Notifier and Listener classes) for this framework can
* be found in the bottom half of the file, the top half contains internal
* implementation concerns.
*
* This implementation can cope with listener event methods with between
* zero and four parameters. This can be extended, by adding extra code
* at the positions marked with (*)
*****************************************************************************/
/*
* A few annoying presentation choices have been made in this file because
* KDOC (2.0.36) falls over. Notably some short classes have been spread
* out, and there is a ";" at the end of the Impl namespace.
*
* Other namespaces in the TSE3 library have a similar problem. In each
* case the trailing ";" is intentional. Sigh.
*/
namespace TSE3
{
template class Listener;
/**************************************************************************
* Notifier implementation details: ignore this bit
*************************************************************************/
/**
* Internal implementation namespace.
*
* You can ignore the classes in this namespace. They are internal
* implementation details for the TSE3 library and are not part of the
* normal public API.
*
* The contents include implementation details for the @ref Notifier class
* and the @ref Impl::Mutex classes which provide TSE3 thread-safety.
*
* @short TSE3 private implementation namespace
* @author Pete Goodliffe
* @version 3.00
* @see Notifier
* @see Listener
* @internal
*/
namespace Impl
{
/**
* This is a minimal default type. It is used by the @ref Event class
* to represent 'no type specified'.
*
* @internal
*/
class def_type
{
};
/**
* This class template is used as a compile time utility to count
* how many parameters have been passed to the @ref Event class
* template. For most types its value is 1. The @ref def_type is the
* only exception, its value is 0.
*
* @internal
*/
template
struct arg_count
{
enum { count=1 };
};
template <>
struct arg_count
{
enum { count=0 };
};
/**
* A type used to overload member functions based on integer
* values. This is used in @ref Event to figure out which
* @ref Event::invokeImpl member function to call.
*
* @internal
*/
template
class num_type
{
};
/**
* A very simple "vector" class. It holds a list of void *s. It
* has enough vector-like methods that the Notifier framework can use
* it.
*
* It is a disgusting thing to include in this library when we should
* just be using std::vector instead. In fact, this class is
* implemented with a std::vector. The reason for its inclusion is the
* fact that it redces the size of libtse3.so.0.0.0 by about a third.
* This can be accounted for by the reduction in size of link names.
*
* @internal
*/
class void_list
{
public:
void_list();
void_list(const void_list &);
~void_list();
/**
* Push a new void* onto the back of the list.
*
* You cannot insert a duplicate entry.
*
* @param p Elemnt to push
* @return Whether the insert was successful: if @p p
* was already inserted, then false will
* be returned.
*/
bool push_back(void *p);
/**
* Returns true if @p p was in the void_list, false otherwise.
*
* @param p Element to remove
* @return True if anything was erased
*/
bool erase(void *p);
/**
* Returns the number of void*s in the void_list.
*
* @return Number of elements in list
*/
unsigned int size() const;
/**
* Returns the void*s at index @p index
*
* @param index Index of item to retrieve
* @return void* at @p index
*/
void *operator[](unsigned int index);
/**
* Returns whether or not @p p is in the void_list
*
* @param p Element to test
* @return Whether @p p is in the void_list
*/
bool contains(void *p) const;
private:
void_list &operator=(const void_list &);
class impl;
impl *pimpl;
};
/**
* The Event class is an internal part of the Notifier/Listener
* framework, used to multicast a particular event to a set of
* Listener objects.
*
* It is a class that can 'hold' zero or more parameters. The upper
* limit is set by the number of template parameters and
* @ref invokeImpl member functions - more can be added if you really
* need them.
*
* If a parameter is not needed, the template declaration will provide
* @ref def_type which counts as 'no parameter'.
*
* @internal
*/
template // (*)
class Event
{
public:
/**
* Ctor. You specify the member function of the
* @p interface_type class to call, and the parameters to call
* it with.
*/
explicit Event(listener_func func,
const p1_type &p1 = p1_type(),
const p2_type &p2 = p2_type(),
const p3_type &p3 = p3_type(),
const p4_type &p4 = p4_type()) // (*)
: func(func), p1(p1), p2(p2), p3(p3), p4(p4) {} // (*)
/**
* Calls 'func' on the specified set of @p listeners.
*
* This function delegates to the appropriate @ref invokeImpl
* method. Which one to call is worked out using the
* @ref arg_count utility.
*/
void callOnEvery(void_list &listeners)
{
const unsigned int argCount = arg_count::count
+ arg_count::count
+ arg_count::count
+ arg_count::count;
// (*)
void_list copy_list(listeners);
for (unsigned int i = 0; i < copy_list.size(); ++i)
{
if (listeners.contains(copy_list[i]))
{
typedef Listener listener_type;
interface_type *listener
= static_cast(copy_list[i]);
invokeImpl(listener, num_type());
}
}
}
private:
/**
* There is a family of invokeImpl classes which will pass the
* parameters to the @p listener. Although we know that the
* callee is of type @p interface_type we abstract it as a
* template type (@p T) so that the functions are not
* instantiated by the compiler unless actually called.
*
* This is vital, since only one of the @ref invokeImpl
* functions will be valid for each event - only that one will
* be instantiated.
*/
template
void invokeImpl(T *listener, num_type<0>) const
{
(void)(listener->*func)();
}
template
void invokeImpl(T *listener, num_type<1>) const
{
(void)(listener->*func)(p1);
}
template
void invokeImpl(T *listener, num_type<2>) const
{
(void)(listener->*func)(p1, p2);
}
template
void invokeImpl(T *listener, num_type<3>) const
{
(void)(listener->*func)(p1, p2, p3);
}
template
void invokeImpl(T *listener, num_type<4>) const
{
(void)(listener->*func)(p1, p2, p3, p4);
} // (*)
const listener_func func;
const p1_type &p1;
const p2_type &p2;
const p3_type &p3;
const p4_type &p4; // (*)
};
};
/**************************************************************************
* Public Notifier framework API
*************************************************************************/
/**
* The Notifier template base class is an implementation of the
* observer design pattern (GoF book).
*
* Objects that need to send out specific callback events derive from this
* base class. Each Notifier class may have any number of callback events,
* and each of these events may take any number (and type) of parameters.
* The callback system is type safe and easy to use.
*
* Events sent from the Notifier class are received by objects of the
* @ref Listener class.
*
* @sect Usage
*
* A separate interface class detailing each callback event and the
* type of Notifier source class must be written, for example:
*
* class Exhibitionist;
*
* class ExhibitionistListener
* {
* public:
* typedef Exhibitionist notifier_type;
*
* virtual void eventOne(Exhibitionist *) = 0;
* virtual void eventTwo(Exhibitionist *, int a) = 0;
* };
*
* The first parameter of callback events must always be a pointer to the
* source object.
*
* You may choose to provide a default implementation for the callback
* methods. This will save you having to reimplement every callback, only
* the ones for the events you are interested in.
*
* You can now declare a @ref Notifier class thus:
*
* class Exhibitionist : public Notifier
* {
* // Other contents....
* };
*
* and implement your functionality.
*
* The Exhibitionist class can send callbacks to any attached listener by
* calling the @ref notify member function. For example:
*
* notify(&ExhibitionistListener::eventTwo, 1);
*
* The source object pointer parameter is automatically provided and does
* not need to be specified.
*
* To receive events from a Notifier class, you must write a class that
* derives from @ref Listener. The documentation for @ref Listener
* describes how to do this.
*
* @short Base class for objects that multicast events to listeners
* @author Pete Goodliffe
* @version 3.00
* @see Listener
*/
template
class Notifier
{
public:
/**
* The type of @ref Listener that this Notifier class works with.
*/
typedef Listener listener_type;
friend class listener_type;
protected:
/**
* Creates a Notifier with no listeners.
*
* Use @ref Listener::attachTo to add listeners.
*
* You can only subclass this type, not instanitate it directly.
*/
Notifier() {}
/**
* The concrete Notifier type (i.e. the class that will
* be subclassing Notifier.
*/
typedef typename interface_type::notifier_type c_notifier_type;
/**
* There is a family of @p notify functions. The allow a Notifier
* to multicast an event to every attached @ref Listener object.
*
* They are overloaded on the different number of parameters each
* listener event function takes.
*
* You call a notify method specifying first the member function of
* the @p interface_type to call, and then the parameters to call
* it with.
*
* Every listener function has a first parameter which is a pointer
* to this (source) object. This is automatically provided by the
* notify function, so you need not supply it. If a funciton takes
* any extra parameters then you must specify them.
*
* The first notify function is for events with no extra
* parameters.
*/
template
void notify(func_type func)
{
typedef
Impl::Event
event_type;
event_type(func, static_cast(this))
.callOnEvery(listeners);
}
/**
* Notify function for events with one extra parameter.
*/
template
void notify(func_type func, const p1_type &p1)
{
typedef
Impl::Event
event_type;
event_type(func, static_cast(this), p1)
.callOnEvery(listeners);
}
/**
* Notify function for events with two extra parameters.
*/
template
void notify(func_type func, const p1_type &p1, const p2_type &p2)
{
typedef
Impl::Event
event_type;
event_type(func, static_cast(this), p1, p2)
.callOnEvery(listeners);
}
/**
* Notify function for events with three extra parameters.
*/
template
void notify(func_type func, const p1_type &p1, const p2_type &p2,
const p3_type &p3)
{
typedef
Impl::Event
event_type;
event_type
(func, static_cast(this), p1, p2, p3)
.callOnEvery(listeners);
} // (*)
/**
* The dtor tells every attached @ref Listener that this object
* is being deleted.
*/
virtual ~Notifier()
{
// LOCK
for (unsigned int i = 0; i < listeners.size(); ++i)
{
listener_type *l
= static_cast(listeners[i]);
l->NotifierImpl_Deleted
(static_cast(this));
}
}
unsigned int numListeners() const { return listeners.size(); }
private:
/**
* Attach a listener to this Notifier object.
*
* You can only attach a given listener object once - any
* subsequent attempt to attach it results in nothing happening;
* it is not an error, but the object will still only ever receive
* one copy of any event.
*
* This function is only used by the @ref Listener class'
* @ref Listener::attachTo method.
*
* @param listener The listener to attach
* @return Whether the attach was successful: if the
* @p listener_type was already attached, then false will
* be returned.
*/
bool attach(listener_type *listener)
{
// LOCK
return listeners.push_back(listener);
}
/**
* Detach a listener from this Notifier object.
*
* If the listener is not already attached no error is raised.
*
* This function is only used by the @ref Listener class'
* @ref Listener::detachFrom method.
*
* @param listener The listener to detach
*/
void detach(listener_type *listener)
{
// LOCK
listeners.erase(listener);
}
typedef Notifier self_type;
Notifier(const self_type &);
self_type &operator=(const self_type &);
Impl::void_list listeners;
};
/**
* The Listener class receives events sent from a @ref Notifier class.
*
* Listener inherits from the specified @p interface_type so you don't need
* to mupltiply inherit from both Listener the @p interface_type in your
* derived class.
*
* To instatiate the Listener class inherit from Listener.
* Following the example in the @ref Notifier documentation, this would
* be:
*
* class Voyeur : public Listener
* {
* public:
* virtual void eventOne(Exhibitionist *src) { ... }
* virtual void eventTwo(Exhibitionist *src, int a) { ... }
* virtual void Notifier_Deleted(Exhibitionist *src)
* {
* // The 'src' is being deleted - you do not need to
* // detach from it, the link is automatically broken.
*
* // This method is not defined in the
* // ExhibitionistListener base class, but is defined by
* // the Listener class. You
* // don't have to implement it if you don't need it.
* }
* };
*
*
* Upon deletion, the Listener class ensures that it is detached from every
* @ref Notifier it has been attached to. This prevents a @ref Notifier
* later trying to callback to an invalid object.
*
* @short Listener interface for @ref Notifier events
* @author Pete Goodliffe
* @version 3.00
* @see Notifier
*/
template
class Listener : public interface_type
{
public:
/**
* The type of @ref Notifier class this listener works with.
*/
typedef Notifier notifier_type;
/**
* Attaches this Listener to @p notifier.
*
* You can only attach to a @ref Notifer object once - any
* subsequent attempt to attach to it results in nothing happening;
* it is not an error, but the Listener will still only ever
* receive one copy of any event.
*
* @param notifier The @ref Notifier object to attach to
*/
void attachTo(notifier_type *notifier)
{
if (notifier->attach(this)) notifiers.push_back(notifier);
}
/**
* Detaches this Listener from @p notifier.
*
* If the listener is not already attached to this @ref Notifier no
* error is raised.
*
* @param notifier The @ref Notifier object to detach from
*/
void detachFrom(notifier_type *notifier)
{
if (notifiers.erase(notifier)) notifier->detach(this);
}
friend class notifier_type;
protected:
/**
* Creates a new Listener which is not attached to any
* @ref Notifier.
*
* Use @ref attachTo to attach to @ref Notifiers.
*
* You can only subclass this type, not instanitate it directly.
*/
Listener() {}
/**
* The concrete Notifier type (i.e. the class that derives from
* Notifier.
*/
typedef typename interface_type::notifier_type c_notifier_type;
/**
* This may be implemented by Listener classes if they care about
* a @ref Notifier object being deleted.
*
* It is called by the @ref Notifer destructor - so the @p notifier
* is in the process of being deleted. Therefore it is not safe to
* call methods on the event source object any more.
*
* You do not need to @ref detachFrom the the @ref Notifier
* since the link is automatically broken for you.
*
* @param notifier The @ref Notifier that has been deleted
*/
virtual void Notifier_Deleted(c_notifier_type * /*notifier*/) {}
/**
* The destructor ensures that the Listener is detached from every
* @ref Notifier it has been attached to.
*
* This means that you don't have to specifically detach from all
* @ref Notifier sources yourself, although it is still considered
* good practice to do so.
*/
virtual ~Listener()
{
// LOCK
for (unsigned int i = 0; i < notifiers.size(); ++i)
{
notifier_type *n
= static_cast(notifiers[i]);
n->detach(this);
}
}
private:
/**
* This is called by the corresponding @ref Notifier type's
* destructor.
*
* This ensures that if a notifier_type that the Listener is
* attached to is deleted, it is removed from the notifiers list,
* so we will never attempt to detach from it later.
*
* It also calls the internal @ref Notifier_Deleted method.
*/
void NotifierImpl_Deleted(c_notifier_type *src)
{
notifiers.erase(static_cast(src));
this->Notifier_Deleted(src);
}
typedef Listener self_type;
Listener(const self_type &);
self_type &operator=(const self_type &);
Impl::void_list notifiers;
};
}
#endif
Generated by: pete on philemon on Wed May 25 14:39:52 2005, using kdoc 2.0a54. |