123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
|
/**
* The signal module provides a basic implementation of the listener pattern
* using the "Signals and Slots" model from Qt.
*
* Copyright: Copyright (C) 2005-2006 Sean Kelly. All rights reserved.
* License: BSD style: $(LICENSE)
* Author: Sean Kelly
*/
module tango.core.Signal;
private import tango.core.Array;
/**
* A signal is an event which contains a collection of listeners (called
* slots). When a signal is called, that call will be propagated to each
* attached slot in a synchronous manner. It is legal for a slot to call a
* signal's attach and detach methods when it is signaled. When this occurs,
* attach events will be queued and processed after the signal has propagated
* to all slots, but detach events are processed immediately. This ensures
* that it is safe for slots to be deleted at any time, even within a slot
* routine.
*
* Example:
* -----------------------------------------------------------------------------
* class Button
* {
* Signal!(Button) press;
* }
*
* void wasPressed( Button b )
* {
* printf( "Button was pressed.\n" );
* }
*
* Button b = new Button;
*
* b.press.attach( &wasPressed );
* b.press( b );
* -----------------------------------------------------------------------------
*
* Please note that this implementation does not use weak pointers to store
* references to slots. This design was chosen because weak pointers are
* inherently unsafe when combined with non-deterministic destruction, with
* many of the same limitations as destructors in the same situation. It is
* still possible to obtain weak-pointer behavior, but this must be done
* through a proxy object instead.
*/
struct Signal( Args... )
{
alias void delegate(Args) SlotDg; ///
alias void function(Args) SlotFn; ///
alias opCall call; /// Alias to simplify chained calling.
/**
* The signal procedure. When called, each of the attached slots will be
* called synchronously.
* Params:
* args = The signal arguments.
*/
void opCall( Args args )
{
synchronized
{
m_blk = true;
for( size_t i = 0; i < m_dgs.length; ++i )
{
if( m_dgs[i] !is null )
m_dgs[i]( args );
}
m_dgs.length = m_dgs.remove( cast(SlotDg) null );
for( size_t i = 0; i < m_fns.length; ++i )
{
if( m_fns[i] !is null )
m_fns[i]( args );
}
m_fns.length = m_fns.remove( cast(SlotFn) null );
m_blk = false;
procAdds();
}
}
/**
* Attaches a delegate to this signal. A delegate may be either attached
* or detached, so successive calls to attach for the same delegate will
* have no effect.
* Params:
* dg = The delegate to attach.
*/
void attach( SlotDg dg )
{
synchronized
{
if( m_blk )
{
m_add ~= Add( dg );
}
else
{
auto pos = m_dgs.find( dg );
if( pos == m_dgs.length )
m_dgs ~= dg;
}
}
}
/**
* Attaches a function to this signal. A function may be either attached
* or detached, so successive calls to attach for the same function will
* have no effect.
* Params:
* fn = The function to attach.
*/
void attach( SlotFn fn )
{
synchronized
{
if( m_blk )
{
m_add ~= Add( fn );
}
else
{
auto pos = m_fns.find( fn );
if( pos == m_fns.length )
m_fns ~= fn;
}
}
}
/**
* Detaches a delegate from this signal.
* Params:
* dg = The delegate to detach.
*/
void detach( SlotDg dg )
{
synchronized
{
auto pos = m_dgs.find( dg );
if( pos < m_dgs.length )
m_dgs[pos] = null;
}
}
/**
* Detaches a function from this signal.
* Params:
* fn = The function to detach.
*/
void detach( SlotFn fn )
{
synchronized
{
auto pos = m_fns.find( fn );
if( pos < m_fns.length )
m_fns[pos] = null;
}
}
private:
struct Add
{
enum Type
{
DG,
FN
}
static Add opCall( SlotDg d )
{
Add e;
e.ty = Type.DG;
e.dg = d;
return e;
}
static Add opCall( SlotFn f )
{
Add e;
e.ty = Type.FN;
e.fn = f;
return e;
}
union
{
SlotDg dg;
SlotFn fn;
}
Type ty;
}
void procAdds()
{
foreach( a; m_add )
{
if( a.ty == Add.Type.DG )
m_dgs ~= a.dg;
else
m_fns ~= a.fn;
}
m_add.length = 0;
}
SlotDg[] m_dgs;
SlotFn[] m_fns;
Add[] m_add;
bool m_blk;
}
debug( UnitTest )
{
unittest
{
class Button
{
Signal!(Button) press;
}
int count = 0;
void wasPressedA( Button b )
{
++count;
}
void wasPressedB( Button b )
{
++count;
}
Button b = new Button;
b.press.attach( &wasPressedA );
b.press( b );
assert( count == 1 );
count = 0;
b.press.attach( &wasPressedB );
b.press( b );
assert( count == 2 );
count = 0;
b.press.attach( &wasPressedA );
b.press( b );
assert( count == 2 );
count = 0;
b.press.detach( &wasPressedB );
b.press( b );
assert( count == 1 );
count = 0;
b.press.detach( &wasPressedA );
b.press( b );
assert( count == 0 );
}
}
|