├── .gitignore ├── prj └── xcode │ └── jl_signal │ └── jl_signal.xcodeproj │ ├── .gitignore │ └── project.pbxproj ├── src ├── testMain.cpp ├── StaticSignalConnectionAllocators.h ├── ScopedAllocator.h ├── Utils.h ├── SignalBase.cpp ├── Signal.h ├── ObjectPoolScopedAllocator.h ├── SignalBase.h ├── DoublyLinkedListTest.cpp ├── ObjectPool.cpp ├── ObjectPool.h ├── DoublyLinkedList.h ├── ObjectPoolTest.cpp └── SignalTest.cpp ├── util ├── generate_signal_definitions_header.rb ├── header_template.erb └── signal_class_template.erb └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | 9 | # Compiled Static libraries 10 | *.lai 11 | *.la 12 | *.a 13 | -------------------------------------------------------------------------------- /prj/xcode/jl_signal/jl_signal.xcodeproj/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *.pbxuser 3 | !default.pbxuser 4 | *.mode1v3 5 | !default.mode1v3 6 | *.mode2v3 7 | !default.mode2v3 8 | *.perspectivev3 9 | !default.perspectivev3 10 | *.xcworkspace 11 | !default.xcworkspace 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | -------------------------------------------------------------------------------- /src/testMain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern void ObjectPoolTest(); 6 | extern void DoublyLinkedListTest(); 7 | extern void SignalTest(); 8 | 9 | int main(int argc, char** argv) 10 | { 11 | srand( (unsigned)time(NULL) ); 12 | 13 | ObjectPoolTest(); 14 | DoublyLinkedListTest(); 15 | SignalTest(); 16 | 17 | printf("\nDone! Press enter to continue...\n"); 18 | getchar(); 19 | } 20 | -------------------------------------------------------------------------------- /src/StaticSignalConnectionAllocators.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_STATIC_SIGNAL_CONNECTION_ALLOCATORS_H_ 2 | #define _JL_STATIC_SIGNAL_CONNECTION_ALLOCATORS_H_ 3 | 4 | #include "ObjectPoolScopedAllocator.h" 5 | #include "SignalDefinitions.h" 6 | 7 | namespace jl { 8 | 9 | typedef Signal0 TDummySignal; 10 | 11 | template< unsigned _Size > 12 | class StaticSignalConnectionAllocator : public StaticObjectPoolAllocator< TDummySignal::eAllocationSize, _Size > 13 | { 14 | }; 15 | 16 | template< unsigned _Size > 17 | class StaticObserverConnectionAllocator : public StaticObjectPoolAllocator< SignalObserver::eAllocationSize, _Size > 18 | { 19 | }; 20 | 21 | } // namespace jl 22 | 23 | #endif // ! defined( _JL_STATIC_SIGNAL_CONNECTION_ALLOCATORS_H_ ) -------------------------------------------------------------------------------- /src/ScopedAllocator.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_SCOPED_ALLOCATOR_H_ 2 | #define _JL_SCOPED_ALLOCATOR_H_ 3 | 4 | // Scoped allocators will most likely be used with placement new, e.g.: 5 | // Foo* pFoo = new( pFooAllocator->Alloc(sizeof(Foo)) ) Foo(); 6 | // To use placement new, we need to include the standard 'new' header. 7 | #include 8 | 9 | namespace jl { 10 | 11 | /** 12 | * An interface for very basic, stateful allocators. No array allocation. 13 | */ 14 | class ScopedAllocator 15 | { 16 | public: 17 | virtual ~ScopedAllocator() {}; 18 | virtual void* Alloc( size_t nBytes ) = 0; 19 | virtual void Free( void* pObject ) = 0; 20 | }; 21 | 22 | } // namespace jl 23 | 24 | #endif // ! defined( _JL_SCOPED_ALLOCATOR_H_ ) 25 | -------------------------------------------------------------------------------- /src/Utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_UTILS_H_ 2 | #define _JL_UTILS_H_ 3 | 4 | ///////////// 5 | // I. Asserts 6 | 7 | #include 8 | 9 | // To disable runtime asserts, either: 10 | // 1. Define JL_DISABLE_ASSERT as a global compiler macro, 11 | // 2. Comment out JL_ENABLE_ASSERT below 12 | #define JL_ENABLE_ASSERT 13 | 14 | #if defined( JL_ENABLE_ASSERT ) && ! defined ( JL_DISABLE_ASSERT ) 15 | #define JL_ASSERT( _Expr ) assert( _Expr ) 16 | #else 17 | #define JL_ASSERT( _Expr ) 18 | #endif 19 | 20 | // A handy compile-time assert 21 | #define JL_COMPILER_ASSERT( _Expr, _Message ) typedef int JL_COMPILER_ASSERT_##_Message[ _Expr ? 0 : -1 ] 22 | 23 | ////////////////////////////// 24 | // II. Miscellaneous utilities 25 | 26 | #define JL_ARRAY_SIZE( _a ) ( sizeof(_a) / sizeof((_a)[0]) ) 27 | #define JL_UNUSED( _a ) (void)( _a ) 28 | 29 | namespace jl { 30 | 31 | template< typename _to, typename _from > 32 | _to BruteForceCast( _from p ) 33 | { 34 | union 35 | { 36 | _from from; 37 | _to to; 38 | } conversion; 39 | conversion.from = p; 40 | return conversion.to; 41 | } 42 | 43 | } // namespace jl 44 | 45 | #endif // ! defined( _JL_UTILS_H_ ) -------------------------------------------------------------------------------- /util/generate_signal_definitions_header.rb: -------------------------------------------------------------------------------- 1 | require 'erubis' 2 | 3 | local_path = File.dirname(__FILE__); 4 | signal_class_template = Erubis::Eruby.new(File.open(File.join(local_path, 'signal_class_template.erb'), 'r').read) 5 | header_template = Erubis::Eruby.new(File.open(File.join(local_path, 'header_template.erb'), 'r').read) 6 | 7 | # Render each signal class 8 | signal_classes = (0..8).map do |arg_count| 9 | binding = { arg_count: arg_count }.merge( 10 | if arg_count == 0 11 | { 12 | template_signature: 'typename _NoParam = void', 13 | arg_type_list: 'void', 14 | arg_signature: 'void', 15 | arg_list: '', 16 | } 17 | else 18 | { 19 | template_signature: (1..arg_count).map{ |i| "typename _P#{i}" }.join(', '), 20 | arg_type_list: (1..arg_count).map{ |i| "_P#{i}" }.join(', '), 21 | arg_signature: (1..arg_count).map{ |i| "_P#{i} p#{i}" }.join(', '), 22 | arg_list: (1..arg_count).map{ |i| "p#{i}" }.join(', '), 23 | } 24 | end 25 | ) 26 | 27 | # Apply the binding to the template, and truncate any empty function calls. 28 | signal_class_template.result(binding).gsub(/\(\s+\)/, '()') 29 | end 30 | 31 | # Output the whole header 32 | puts header_template.result(:signal_classes => signal_classes).strip -------------------------------------------------------------------------------- /util/header_template.erb: -------------------------------------------------------------------------------- 1 | #ifndef _JL_SIGNAL_DEFINITIONS_H_ 2 | #define _JL_SIGNAL_DEFINITIONS_H_ 3 | 4 | #include "FastDelegate.h" 5 | #include "Utils.h" 6 | #include "SignalBase.h" 7 | 8 | /** 9 | * The content of the following classes (Signal0 -> Signal8) is identical, 10 | * except for the number of parameters specified in the Connect() and Emit() 11 | * functions. 12 | */ 13 | 14 | #ifdef JL_SIGNAL_ENABLE_LOGSPAM 15 | #include 16 | #define JL_SIGNAL_LOG( ... ) printf( __VA_ARGS__ ) 17 | #else 18 | #define JL_SIGNAL_LOG( ... ) 19 | #endif 20 | 21 | #if defined( JL_SIGNAL_ASSERT_ON_DOUBLE_CONNECT ) 22 | #define JL_SIGNAL_DOUBLE_CONNECTED_FUNCTION_ASSERT( _function ) JL_ASSERT( ! IsConnected(_function) ) 23 | #define JL_SIGNAL_DOUBLE_CONNECTED_INSTANCE_METHOD_ASSERT( _obj, _method ) JL_ASSERT( ! IsConnected(_obj, _method) ) 24 | #else 25 | #define JL_SIGNAL_DOUBLE_CONNECTED_FUNCTION_ASSERT( _function ) 26 | #define JL_SIGNAL_DOUBLE_CONNECTED_INSTANCE_METHOD_ASSERT( _obj, _method ) 27 | #endif 28 | 29 | namespace jl { 30 | 31 | #ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX 32 | // Forward-declare a variable-signature template 33 | template 34 | class Signal; 35 | #endif 36 | 37 | <%= signal_classes.map{ |c| c.strip }.join("\n\n") %> 38 | 39 | } // namespace jl 40 | 41 | #endif // ! defined( _JL_SIGNAL_DEFINITIONS_H_ ) -------------------------------------------------------------------------------- /src/SignalBase.cpp: -------------------------------------------------------------------------------- 1 | #include "Signal.h" 2 | 3 | using namespace jl; 4 | 5 | ScopedAllocator* jl::SignalObserver::s_pCommonAllocator = NULL; 6 | ScopedAllocator* SignalBase::s_pCommonAllocator = NULL; 7 | 8 | jl::SignalObserver::~SignalObserver() 9 | { 10 | DisconnectAllSignals(); 11 | } 12 | 13 | void jl::SignalObserver::DisconnectSignal( SignalBase* pSignal ) 14 | { 15 | for ( SignalList::iterator i = m_oSignals.begin(); i.isValid(); ) 16 | { 17 | if ( *i == pSignal ) 18 | { 19 | JL_SIGNAL_LOG( "Observer %p disconnecting signal %p", this, pSignal ); 20 | i->OnObserverDisconnect( this ); 21 | break; 22 | } 23 | else 24 | { 25 | ++i; 26 | } 27 | } 28 | } 29 | 30 | void jl::SignalObserver::DisconnectAllSignals() 31 | { 32 | JL_SIGNAL_LOG( "Observer %p disconnecting all signals\n", this ); 33 | 34 | for ( SignalList::iterator i = m_oSignals.begin(); i.isValid(); ++i ) 35 | { 36 | i->OnObserverDisconnect( this ); 37 | } 38 | 39 | m_oSignals.Clear(); 40 | } 41 | 42 | void jl::SignalObserver::OnSignalConnect( SignalBase* pSignal ) 43 | { 44 | JL_SIGNAL_LOG( "\tObserver %p received connection message from signal %p\n", this, pSignal ); 45 | const bool bAdded = m_oSignals.Add( pSignal ); 46 | JL_ASSERT( bAdded ); 47 | } 48 | 49 | void jl::SignalObserver::OnSignalDisconnect( SignalBase* pSignal ) 50 | { 51 | JL_SIGNAL_LOG( "\tObserver %p received disconnect message from signal %p\n", this, pSignal ); 52 | 53 | OnSignalDisconnectInternal( pSignal ); 54 | 55 | for ( SignalList::iterator i = m_oSignals.begin(); i.isValid(); ) 56 | { 57 | if ( *i == pSignal ) 58 | { 59 | JL_SIGNAL_LOG( "\t\tRemoving connection to signal %p\n", pSignal ); 60 | m_oSignals.Remove( i ); 61 | } 62 | else 63 | { 64 | ++i; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Signal.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_SIGNAL_H_ 2 | #define _JL_SIGNAL_H_ 3 | 4 | #include "FastDelegate.h" 5 | 6 | // Uncomment this to see verbose console messages about Signal/SignalObserver 7 | // connections and disconnections. Recommended for debug only. 8 | //#define JL_SIGNAL_ENABLE_LOGSPAM 9 | 10 | // Uncomment this to force assertion failures when a SignalObserver tries 11 | // to connect the same slot to the same signal twice. Recommended for debug only. 12 | //#define JL_SIGNAL_ASSERT_ON_DOUBLE_CONNECT 13 | 14 | /** 15 | * Quick usage guide: 16 | * 17 | * Use Signal to call an arbitrary number of functions with a single compile-time function call. 18 | * Currently, Signal only supports calls to object-method pairs, i.e., it is not possible to call 19 | * a static, non-instance function using Signal. 20 | * 21 | * There are three ways of declaring a signal: 22 | * 23 | * - Base signal class, with parameter count in the type name: 24 | * Signal2< int, int > mySignal; 25 | * 26 | * - Template wrapper, using function signature syntax: 27 | * Signal< void(int, int) > mySignal; 28 | * 29 | * - Template wrapper + macro (easiest, slightly hacky): 30 | * JL_SIGNAL( int, int ) mySignal; 31 | */ 32 | 33 | #include "SignalDefinitions.h" 34 | 35 | /** 36 | * The following macro will allow you to ignore the argument 37 | * count in the signal typename. Thus, instead of writing this: 38 | * 39 | * Signal1< int, int > SignalA; 40 | * Signal3< int, char, float > SignalB; 41 | * 42 | * you can write this: 43 | * 44 | * JL_SIGNAL( int, int ) SignalA; 45 | * JL_SIGNAL( int, char, float ) SignalB; 46 | * 47 | * Note: this macro evaluates to a TYPE, so it can be used in typedefs. 48 | */ 49 | #ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX 50 | #define JL_SIGNAL( ... ) jl::Signal< void( __VA_ARGS__ ) > 51 | #else 52 | #define JL_SIGNAL( ... ) JL_COMPILER_ASSERT( false, Your_Compiler_Does_Not_Support_This_Syntax ) 53 | #endif // ! defined( FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX ) 54 | 55 | #endif // ! defined( _JL_SIGNAL_H_ ) -------------------------------------------------------------------------------- /src/ObjectPoolScopedAllocator.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_OBJECT_POOL_SCOPED_ALLOCATOR_H_ 2 | #define _JL_OBJECT_POOL_SCOPED_ALLOCATOR_H_ 3 | 4 | #include "ScopedAllocator.h" 5 | #include "ObjectPool.h" 6 | 7 | namespace jl { 8 | 9 | /** 10 | * Object pool wrappers that provide ScopedAllocator interfaces 11 | */ 12 | 13 | class PreallocatedObjectPoolAllocator : public ScopedAllocator 14 | { 15 | public: 16 | // Initialize object pool with preallocated buffer. 17 | // If you set the PreallocatedObjectPool::eFlag_ManageBuffer flag, make sure 18 | // that the buffer was created using array-new. 19 | void Init( void* pBuffer, unsigned nCapacity, unsigned nStride, unsigned nFlags ) 20 | { 21 | m_oPool.Init( pBuffer, nCapacity, nStride, nFlags ); 22 | } 23 | 24 | void Deinit() 25 | { 26 | m_oPool.Deinit(); 27 | } 28 | 29 | unsigned CountAllocations() const 30 | { 31 | return m_oPool.CountAllocations(); 32 | } 33 | 34 | // Virtual overrides 35 | void* Alloc( size_t nBytes ) 36 | { 37 | JL_ASSERT( nBytes <= m_oPool.GetStride() ); 38 | return m_oPool.Alloc(); 39 | } 40 | 41 | void Free( void* pObject ) 42 | { 43 | m_oPool.Free( pObject ); 44 | } 45 | 46 | private: 47 | PreallocatedObjectPool m_oPool; 48 | }; 49 | 50 | template 51 | class StaticObjectPoolAllocator : public ScopedAllocator 52 | { 53 | public: 54 | typedef StaticObjectPool<_Stride, _Capacity> TObjectPool; 55 | 56 | unsigned CountAllocations() const 57 | { 58 | return m_oPool.CountAllocations(); 59 | } 60 | 61 | // Virtual overrides 62 | void* Alloc( size_t nBytes ) 63 | { 64 | JL_ASSERT( nBytes <= m_oPool.GetStride() ); 65 | return m_oPool.Alloc(); 66 | } 67 | 68 | void Free( void* pObject ) 69 | { 70 | m_oPool.Free( pObject ); 71 | } 72 | 73 | private: 74 | TObjectPool m_oPool; 75 | }; 76 | 77 | } // namespace jl 78 | 79 | #endif // ! defined( _JL_OBJECT_POOL_SCOPED_ALLOCATOR_H_ ) 80 | -------------------------------------------------------------------------------- /src/SignalBase.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_SIGNAL_BASE_H_ 2 | #define _JL_SIGNAL_BASE_H_ 3 | 4 | #include "Utils.h" 5 | #include "DoublyLinkedList.h" 6 | 7 | namespace jl { 8 | 9 | // Forward declarations 10 | class SignalBase; 11 | 12 | // Derive from this class to receive signals 13 | class SignalObserver 14 | { 15 | // Public interface 16 | public: 17 | virtual ~SignalObserver(); 18 | 19 | void DisconnectAllSignals(); 20 | void DisconnectSignal( SignalBase* pSignal ); 21 | 22 | void SetConnectionAllocator( ScopedAllocator* pAllocator ) { m_oSignals.Init( pAllocator ); } 23 | unsigned CountSignalConnections() const { return m_oSignals.Count(); } 24 | 25 | // Interface for child classes 26 | protected: 27 | // Disallow instances of this class 28 | SignalObserver() { SetConnectionAllocator( s_pCommonAllocator ); } 29 | SignalObserver( ScopedAllocator* pAllocator ) { SetConnectionAllocator( pAllocator ); } 30 | 31 | // Hmm, a bit of a hack, but if a derived type caches pointers to signals, 32 | // we may need this 33 | virtual void OnSignalDisconnectInternal( SignalBase* pSignal ) { JL_UNUSED(pSignal); } 34 | 35 | // Private interface (to SignalBase) 36 | private: 37 | friend class SignalBase; 38 | 39 | void OnSignalConnect( SignalBase* pSignal ); 40 | void OnSignalDisconnect( SignalBase* pSignal ); 41 | 42 | // Signal list 43 | public: 44 | typedef DoublyLinkedList SignalList; 45 | enum { eAllocationSize = sizeof(SignalList::Node) }; 46 | 47 | private: 48 | SignalList m_oSignals; 49 | 50 | // Global allocator 51 | public: 52 | static void SetCommonConnectionAllocator( ScopedAllocator* pAllocator ) { s_pCommonAllocator = pAllocator; } 53 | 54 | private: 55 | static ScopedAllocator* s_pCommonAllocator; 56 | }; 57 | 58 | class SignalBase 59 | { 60 | public: 61 | virtual ~SignalBase() {}; 62 | 63 | virtual unsigned CountConnections() const = 0; 64 | 65 | // Interface for derived signal classes 66 | protected: 67 | // Disallow instances of this class 68 | SignalBase() {} 69 | 70 | // Called on any connection to the observer. 71 | void NotifyObserverConnect( SignalObserver* pObserver ) { pObserver->OnSignalConnect(this); } 72 | 73 | // Called when no more connections exist to the observer. 74 | void NotifyObserverDisconnect( SignalObserver* pObserver ) { pObserver->OnSignalDisconnect(this); } 75 | 76 | // Private interface (for SignalObserver) 77 | private: 78 | friend class SignalObserver; 79 | virtual void OnObserverDisconnect( SignalObserver* pObserver ) = 0; 80 | 81 | // Global allocator 82 | public: 83 | static void SetCommonConnectionAllocator( ScopedAllocator* pAllocator ) { s_pCommonAllocator = pAllocator; } 84 | 85 | protected: 86 | static ScopedAllocator* s_pCommonAllocator; 87 | }; 88 | 89 | } // namespace jl 90 | 91 | #endif // ! defined( _JL_SIGNAL_BASE_H_ ) -------------------------------------------------------------------------------- /src/DoublyLinkedListTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "DoublyLinkedList.h" 6 | #include "ObjectPoolScopedAllocator.h" 7 | 8 | using namespace jl; 9 | 10 | namespace 11 | { 12 | typedef DoublyLinkedList StringList; 13 | typedef StaticObjectPoolAllocator< sizeof(StringList::Node), 100 > StringNodeAllocator; 14 | } 15 | 16 | void DoublyLinkedListTest() 17 | { 18 | const char* pTestStrings[] = 19 | { 20 | "Test 1", 21 | "Test 2", 22 | "Test 3", 23 | "Test 4", 24 | "Test 5", 25 | "Test 6", 26 | "Test 7", 27 | "Test 8", 28 | "Test 9", 29 | "Test 10", 30 | "Test 11", 31 | "Test 12", 32 | "Test 13", 33 | "Test 14", 34 | "Test 15", 35 | "Test 16", 36 | "Test 17", 37 | "Test 18", 38 | "Test 19", 39 | "Test 20", 40 | "Test 21", 41 | "Test 22", 42 | "Test 23", 43 | "Test 24", 44 | "Test 25", 45 | "Test 26", 46 | "Test 27", 47 | "Test 28", 48 | "Test 29", 49 | "Test 30", 50 | "Test 31", 51 | "Test 32", 52 | }; 53 | 54 | StringNodeAllocator oAllocator; 55 | StringList oList; 56 | 57 | oList.Init( & oAllocator ); 58 | 59 | // Insertion test 60 | printf( "Inserting objects...\n" ); 61 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(pTestStrings); ++i ) 62 | { 63 | assert( oList.Add(pTestStrings[i]) ); 64 | } 65 | 66 | // Test object count 67 | assert( oList.Count() == JL_ARRAY_SIZE(pTestStrings) ); 68 | 69 | // Iterator test 70 | printf( "Iterating through list...\n" ); 71 | for ( StringList::iterator i = oList.begin(); i.isValid(); ++i ) 72 | { 73 | printf( "\tObject: %s\n", *i ); 74 | } 75 | 76 | // Value-based removal 77 | printf( "Value-based removal...\n" ); 78 | 79 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(pTestStrings); ++i ) 80 | { 81 | printf( "\tRemoving: %s\n", pTestStrings[i] ); 82 | assert( oList.Remove(pTestStrings[i]) ); 83 | } 84 | 85 | // Test object count 86 | assert( oList.Count() == 0 ); 87 | assert( oAllocator.CountAllocations() == 0 ); 88 | 89 | // Value-based reverse removal 90 | printf( "Value-based reverse removal...\n" ); 91 | 92 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(pTestStrings); ++i ) 93 | { 94 | assert( oList.Add(pTestStrings[i]) ); 95 | } 96 | 97 | for ( unsigned i = JL_ARRAY_SIZE(pTestStrings); i > 0; --i ) 98 | { 99 | printf( "\tRemoving: %s\n", pTestStrings[i - 1] ); 100 | assert( oList.Remove(pTestStrings[i - 1]) ); 101 | } 102 | 103 | // Test object count 104 | assert( oList.Count() == 0 ); 105 | assert( oAllocator.CountAllocations() == 0 ); 106 | 107 | // Iterator-based removal 108 | printf( "Iterator-based removal...\n" ); 109 | 110 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(pTestStrings); ++i ) 111 | { 112 | assert( oList.Add(pTestStrings[i]) ); 113 | } 114 | 115 | for ( StringList::iterator i = oList.begin(); i.isValid(); ) 116 | { 117 | printf( "\tRemoving: %s\n", *i ); 118 | assert( oList.Remove(i) ); 119 | } 120 | 121 | // Test object count 122 | assert( oList.Count() == 0 ); 123 | assert( oAllocator.CountAllocations() == 0 ); 124 | 125 | // Random removal 126 | enum { eRandomTrials = 16 }; 127 | printf( "\nStarting %d random removal tests\n", eRandomTrials ); 128 | 129 | for ( unsigned i = 0; i < 5; ++i ) 130 | { 131 | const unsigned nInsert = ( rand() % JL_ARRAY_SIZE(pTestStrings) ) + 1; 132 | const unsigned nRemove = ( rand() % nInsert ) + 1; 133 | 134 | printf( "\tTrial %d: inserting %d objects and removing %d objects\n", i + 1, nInsert, nRemove ); 135 | 136 | // Insert objects 137 | printf( "\t\tInserting %d objects\n", nInsert ); 138 | for ( unsigned j = 0; j < nInsert; ++j ) 139 | { 140 | printf( "\t\t\tInserting %s\n", pTestStrings[j] ); 141 | assert( oList.Add(pTestStrings[j]) ); 142 | } 143 | 144 | // Remove objects 145 | printf( "\t\tRemoving %d objects\n", nRemove ); 146 | for ( unsigned j = 0; j < nRemove; ++j ) 147 | { 148 | // Create iterator and seek to random position 149 | const unsigned nSeek = rand() % oList.Count(); 150 | StringList::iterator iter = oList.begin(); 151 | 152 | for ( unsigned k = 0; k < nSeek; ++k ) 153 | { 154 | ++iter; 155 | } 156 | 157 | // Remove object from list 158 | printf( "\t\t\tRemoving item ID%d: %s\n", nSeek, *iter ); 159 | assert( oList.Remove(iter) ); 160 | } 161 | 162 | // Display leftovers 163 | printf( "\t\tRemaining objects (%d):\n", oList.Count() ); 164 | for ( StringList::iterator i = oList.begin(); i.isValid(); ++i ) 165 | { 166 | printf( "\t\t\tObject: %s\n", *i ); 167 | } 168 | 169 | // Test object count 170 | const unsigned nCount = nInsert - nRemove; 171 | assert( oList.Count() == nCount ); 172 | assert( oAllocator.CountAllocations() == nCount ); 173 | 174 | // Clear 175 | oList.Clear(); 176 | 177 | // Test object count 178 | assert( oList.Count() == 0 ); 179 | assert( oAllocator.CountAllocations() == 0 ); 180 | } 181 | } -------------------------------------------------------------------------------- /src/ObjectPool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ObjectPool.h" 4 | 5 | namespace jl { 6 | // Some helper routines 7 | namespace 8 | { 9 | bool IsBounded( const void* pObject, const unsigned char* pObjectBuffer, unsigned nCapacity, unsigned nStride ) 10 | { 11 | const unsigned char* const pFirst = pObjectBuffer; 12 | const unsigned char* const pLast = pObjectBuffer + nStride * (nCapacity - 1); 13 | return pFirst <= pObject && pObject <= pLast; 14 | } 15 | 16 | bool IsAligned( const void* pObject, const unsigned char* pObjectBuffer, unsigned nStride ) 17 | { 18 | const ptrdiff_t nDiff = reinterpret_cast(pObject) - pObjectBuffer; 19 | return ( nDiff % nStride == 0 ); 20 | } 21 | 22 | // Populate a sorted array with pointers to every free node, and return the number of free nodes. 23 | unsigned GetSortedFreeNodeList( ObjectPool::FreeNode* ppSortedFreeNodes[], ObjectPool::FreeNode* pFreeListHead ) 24 | { 25 | unsigned nFreeCount = 0; 26 | 27 | for ( ObjectPool::FreeNode* n = pFreeListHead; n != NULL; n = n->pNextFree ) 28 | { 29 | ppSortedFreeNodes[ nFreeCount ] = n; 30 | nFreeCount += 1; 31 | } 32 | 33 | // Insertion sort 34 | for ( unsigned i = 1; i < nFreeCount; ++i ) 35 | { 36 | ObjectPool::FreeNode* pCurrent = ppSortedFreeNodes[ i ]; 37 | unsigned j = i; 38 | 39 | // Insert pCurrent into the appropriate spot in the span [0, i] 40 | while ( j > 0 && ppSortedFreeNodes[j - 1] > pCurrent ) 41 | { 42 | ppSortedFreeNodes[ j ] = ppSortedFreeNodes[ j - 1 ]; 43 | --j; 44 | } 45 | 46 | ppSortedFreeNodes[ j ] = pCurrent; 47 | } 48 | 49 | return nFreeCount; 50 | } 51 | } // anon namespace 52 | } // namespace jl 53 | 54 | /////////////////////////////////////////////////////////////////////////////// 55 | /////////////////////////////////////////////////////////////////////////////// 56 | 57 | // Initializes an object buffer as a free list and returns the head of the list 58 | jl::ObjectPool::FreeNode* jl::ObjectPool::InitFreeList( unsigned char* pObjectBuffer, unsigned nCapacity, unsigned nStride ) 59 | { 60 | // Setup free list links 61 | unsigned char* const pLast = pObjectBuffer + nStride * (nCapacity - 1); 62 | 63 | for ( unsigned char* pCurrent = pObjectBuffer; pCurrent < pLast; pCurrent += nStride ) 64 | { 65 | FreeNode::Cast( pCurrent )->pNextFree = FreeNode::Cast( pCurrent + nStride ); 66 | } 67 | 68 | // End free list 69 | FreeNode::Cast( pLast )->pNextFree = NULL; 70 | 71 | // Return start of free list 72 | return FreeNode::Cast( pObjectBuffer ); 73 | } 74 | 75 | unsigned jl::ObjectPool::FreeListSize( ObjectPool::FreeNode* pFreeListHead ) 76 | { 77 | // Early out for degenerate case 78 | if ( ! pFreeListHead ) return 0; 79 | 80 | unsigned n = 0; 81 | for ( ; pFreeListHead; pFreeListHead = pFreeListHead->pNextFree ) 82 | { 83 | ++n; 84 | } 85 | 86 | return n; 87 | } 88 | 89 | bool jl::ObjectPool::IsBoundedAndAligned( const void* pObject, const unsigned char* pObjectBuffer, unsigned nCapacity, unsigned nStride ) 90 | { 91 | return IsBounded( pObject, pObjectBuffer, nCapacity, nStride ) 92 | && IsAligned( pObject, pObjectBuffer, nStride ); 93 | } 94 | 95 | bool jl::ObjectPool::IsFree( const void* pObject, const FreeNode* pFreeListHead ) 96 | { 97 | // Scrub through free list and make sure this object hasn't been freed already 98 | for ( const ObjectPool::FreeNode* n = pFreeListHead; n != NULL; n = n->pNextFree ) 99 | { 100 | if ( n == pObject ) 101 | { 102 | return true; 103 | } 104 | } 105 | 106 | return false; 107 | } 108 | 109 | /////////////////////////////////////////////////////////////////////////////// 110 | /////////////////////////////////////////////////////////////////////////////// 111 | 112 | jl::PreallocatedObjectPool::PreallocatedObjectPool() 113 | { 114 | Reset(); 115 | } 116 | 117 | jl::PreallocatedObjectPool::PreallocatedObjectPool( void* pBuffer, unsigned nCapacity, unsigned nStride, unsigned nFlags /*= eFlag_Default */ ) 118 | { 119 | m_pObjectBuffer = NULL; // prevent assertion failure in Init() 120 | Init( pBuffer, nCapacity, nStride, nFlags ); 121 | } 122 | 123 | jl::PreallocatedObjectPool::~PreallocatedObjectPool() 124 | { 125 | if ( m_nFlags & eFlag_ManageBuffer ) 126 | { 127 | delete[] m_pObjectBuffer; 128 | } 129 | } 130 | 131 | void jl::PreallocatedObjectPool::Init( void* pBuffer, unsigned nCapacity, unsigned nStride, unsigned nFlags /*= eFlag_Default */ ) 132 | { 133 | JL_ASSERT( m_pObjectBuffer == NULL ); 134 | 135 | m_pObjectBuffer = (unsigned char*)pBuffer; 136 | m_pFreeListHead = ObjectPool::InitFreeList( m_pObjectBuffer, nCapacity, nStride ); 137 | 138 | m_nCapacity = nCapacity; 139 | m_nStride = nStride; 140 | m_nFlags = nFlags; 141 | } 142 | 143 | void jl::PreallocatedObjectPool::Deinit() 144 | { 145 | if ( m_nFlags & eFlag_ManageBuffer ) 146 | { 147 | delete[] m_pObjectBuffer; 148 | } 149 | 150 | Reset(); 151 | } 152 | 153 | void jl::PreallocatedObjectPool::Reset() 154 | { 155 | m_pObjectBuffer = NULL; 156 | m_pFreeListHead = NULL; 157 | 158 | m_nCapacity = 0; 159 | m_nAllocations = 0; 160 | m_nStride = 0; 161 | m_nFlags = 0; 162 | } 163 | -------------------------------------------------------------------------------- /src/ObjectPool.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_OBJECTPOOL_H_ 2 | #define _JL_OBJECTPOOL_H_ 3 | 4 | #include "Utils.h" 5 | 6 | //#define JL_OBJECT_POOL_ENABLE_FREELIST_CHECK 7 | 8 | namespace jl { 9 | 10 | /** 11 | * A family of object pool classes: 12 | * 13 | * PreallocatedObjectPool 14 | * StaticObjectPool 15 | * 16 | * Due to data alignment issues, this does not derive from the ScopedAllocator 17 | * interface. If you need an object pool to act as a ScopedAllocator, please 18 | * see ObjectPoolScopedAllocator.h. 19 | * 20 | * PRO: 21 | * O(1) allocate and free 22 | * O(1) overhead 23 | * 24 | * CON: 25 | * No support for array-new allocation 26 | * Free()/Destroy() requires knowledge of which pool a pointer was allocated from 27 | */ 28 | 29 | // A common implementation for our object pools. 30 | namespace ObjectPool 31 | { 32 | struct FreeNode 33 | { 34 | FreeNode* pNextFree; 35 | 36 | static FreeNode* Cast( unsigned char* pRaw ) { return reinterpret_cast( pRaw ); } 37 | static FreeNode* Cast( void* pRaw ) { return reinterpret_cast( pRaw ); } 38 | static const FreeNode* Cast( const unsigned char* pRaw ) { return reinterpret_cast( pRaw ); } 39 | static const FreeNode* Cast( const void* pRaw ) { return reinterpret_cast( pRaw ); } 40 | }; 41 | 42 | // Return first node in the free list, and advances the free list to the next node 43 | inline void* Alloc( FreeNode*& pFreeListHead ) 44 | { 45 | if ( pFreeListHead == NULL ) 46 | { 47 | return NULL; 48 | } 49 | 50 | void* pObject = pFreeListHead; 51 | pFreeListHead = pFreeListHead->pNextFree; 52 | 53 | return pObject; 54 | } 55 | 56 | // Return object's node to the free list. Does no address validation or object destruction. 57 | inline void Free( void* pObject, FreeNode*& pFreeListHead ) 58 | { 59 | FreeNode* pNode = FreeNode::Cast( pObject ); 60 | pNode->pNextFree = pFreeListHead; 61 | pFreeListHead = pNode; 62 | } 63 | 64 | // Initializes an object buffer as a free list and returns the head of the list 65 | FreeNode* InitFreeList( unsigned char* pObjectBuffer, unsigned nCapacity, unsigned nStride ); 66 | unsigned FreeListSize( FreeNode* pFreeListHead ); 67 | 68 | // Returns true if an object is allocated to the given object pool 69 | bool IsBoundedAndAligned( const void* pObject, const unsigned char* pObjectBuffer, unsigned nCapacity, unsigned nStride ); 70 | bool IsFree( const void* pObject, const FreeNode* pFreeListHead ); 71 | }; 72 | 73 | /** 74 | * A class that manages allocations to a pre-allocated object buffer. 75 | * Useful for pools whose size are NOT known at compile-time. 76 | * 77 | * By default, this class will free the supplied buffer when the pool object is destroy. 78 | * If this behavior is enabled, make sure that the supplied buffer was created using array-new. 79 | */ 80 | class PreallocatedObjectPool 81 | { 82 | public: 83 | enum { 84 | eFlag_ManageBuffer = 0x01, 85 | eFlag_Defaults = eFlag_ManageBuffer, 86 | }; 87 | 88 | PreallocatedObjectPool(); 89 | PreallocatedObjectPool( void* pBuffer, unsigned nCapacity, unsigned nStride, unsigned nFlags = eFlag_Defaults ); 90 | 91 | ~PreallocatedObjectPool(); 92 | 93 | // Initialize object pool with preallocated buffer. If the _ManageBuffer template parameter is set, 94 | // you should allocate this buffer using an array-new. 95 | void Init( void* pBuffer, unsigned nCapacity, unsigned nStride, unsigned nFlags = eFlag_Defaults ); 96 | void Deinit(); 97 | 98 | // Allocates memory. Does not call constructor--you should do a placement new on the returned pointer. 99 | void* Alloc() 100 | { 101 | JL_ASSERT( m_pObjectBuffer ); 102 | void* p = ObjectPool::Alloc( m_pFreeListHead ); 103 | 104 | if ( p ) 105 | { 106 | m_nAllocations++; 107 | } 108 | 109 | return p; 110 | } 111 | 112 | // Free allocated memory, with error checking. Does NOT call destructor. 113 | void Free( void* pObject ) 114 | { 115 | JL_ASSERT( m_pObjectBuffer ); 116 | JL_ASSERT( ObjectPool::IsBoundedAndAligned(pObject, m_pObjectBuffer, m_nCapacity, m_nStride) ); 117 | #ifdef JL_OBJECT_POOL_ENABLE_FREELIST_CHECK 118 | JL_ASSERT( ! ObjectPool::IsFree(pObject, m_pFreeListHead) ); 119 | #endif 120 | 121 | ObjectPool::Free( pObject, m_pFreeListHead ); 122 | m_nAllocations--; 123 | } 124 | 125 | // Accessors 126 | unsigned char* GetObjectBuffer() { return m_pObjectBuffer; } 127 | const unsigned char* GetObjectBuffer() const { return m_pObjectBuffer; } 128 | 129 | unsigned GetCapacity() const { return m_nCapacity; } 130 | unsigned GetStride() const { return m_nStride; } 131 | unsigned CountAllocations() const { return m_nAllocations; } 132 | 133 | bool IsEmpty() const { return m_nAllocations == 0; } 134 | bool IsFull() const { return m_nAllocations == m_nCapacity; } 135 | 136 | ObjectPool::FreeNode* GetFreeListHead() { return m_pFreeListHead; } 137 | const ObjectPool::FreeNode* GetFreeListHead() const { return m_pFreeListHead; } 138 | 139 | private: 140 | void Reset(); 141 | 142 | unsigned char* m_pObjectBuffer; 143 | ObjectPool::FreeNode* m_pFreeListHead; 144 | unsigned m_nCapacity; 145 | unsigned m_nStride; 146 | unsigned m_nAllocations; 147 | unsigned m_nFlags; 148 | }; 149 | 150 | /** 151 | * An object pool with an internal buffer. 152 | * Useful for pools whose size are known at compile-time. 153 | * 154 | * Due to alignment issues, this is not implemented in terms of PreallocatedObjectPool. 155 | */ 156 | template 157 | class StaticObjectPool 158 | { 159 | public: 160 | // For static object pools, the capacity and stride are type constants. 161 | enum { 162 | eStride = _Stride, 163 | eCapacity = _Capacity, 164 | }; 165 | 166 | StaticObjectPool() 167 | { 168 | m_pFreeListHead = ObjectPool::InitFreeList( m_pObjectBuffer, eCapacity, eStride ); 169 | m_nAllocations = 0; 170 | } 171 | 172 | // Allocates memory. Does not call constructor--you should do a placement new on the returned pointer. 173 | void* Alloc() 174 | { 175 | void* p = ObjectPool::Alloc( m_pFreeListHead ); 176 | 177 | if ( p ) 178 | { 179 | m_nAllocations++; 180 | } 181 | 182 | return p; 183 | } 184 | 185 | // Free allocated memory, with error checking. Does NOT call destructor. 186 | void Free( void* pObject ) 187 | { 188 | JL_ASSERT( ObjectPool::IsBoundedAndAligned(pObject, m_pObjectBuffer, eCapacity, eStride) ); 189 | #ifdef JL_OBJECT_POOL_ENABLE_FREELIST_CHECK 190 | JL_ASSERT( ! ObjectPool::IsFree(pObject, m_pFreeListHead) ); 191 | #endif 192 | 193 | ObjectPool::Free( pObject, m_pFreeListHead ); 194 | m_nAllocations--; 195 | } 196 | 197 | // Accessors 198 | unsigned char* GetObjectBuffer() { return m_pObjectBuffer; } 199 | const unsigned char* GetObjectBuffer() const { return m_pObjectBuffer; } 200 | 201 | unsigned GetCapacity() const { return eCapacity; } 202 | unsigned CountAllocations() const { return m_nAllocations; } 203 | unsigned GetStride() const { return eStride; } 204 | 205 | ObjectPool::FreeNode* GetFreeListHead() { return m_pFreeListHead; } 206 | const ObjectPool::FreeNode* GetFreeListHead() const { return m_pFreeListHead; } 207 | 208 | bool IsEmpty() const { return m_nAllocations == 0; } 209 | bool IsFull() const { return m_nAllocations == eCapacity; } 210 | 211 | private: 212 | unsigned char m_pObjectBuffer[ eCapacity * eStride ]; 213 | ObjectPool::FreeNode* m_pFreeListHead; 214 | unsigned m_nAllocations; 215 | }; 216 | 217 | } // namespace jl 218 | 219 | #endif // ! defined( _JL_OBJECTPOOL_H_ ) 220 | -------------------------------------------------------------------------------- /src/DoublyLinkedList.h: -------------------------------------------------------------------------------- 1 | #ifndef _JL_DOUBLY_LINKED_LIST_H_ 2 | #define _JL_DOUBLY_LINKED_LIST_H_ 3 | 4 | #include "Utils.h" 5 | #include "ScopedAllocator.h" 6 | 7 | namespace jl { 8 | 9 | /** 10 | * Your basic doubly-linked list, with link nodes allocated outside of the 11 | * contained type. 12 | * Requires a ScopedAllocator for node allocation. 13 | */ 14 | template 15 | class DoublyLinkedList 16 | { 17 | public: 18 | 19 | ////////////////// 20 | // Data structures 21 | ////////////////// 22 | 23 | typedef _T TObject; 24 | 25 | struct Node 26 | { 27 | TObject object; 28 | Node* prev; 29 | Node* next; 30 | }; 31 | 32 | class iterator 33 | { 34 | public: 35 | TObject& operator*() 36 | { 37 | return m_pCurrent->object; 38 | } 39 | 40 | TObject& operator->() 41 | { 42 | return m_pCurrent->object; 43 | } 44 | 45 | iterator& operator--() 46 | { 47 | JL_ASSERT( m_pCurrent ); 48 | 49 | if ( m_pCurrent ) 50 | { 51 | m_pCurrent = m_pCurrent->prev; 52 | } 53 | 54 | return *this; 55 | } 56 | 57 | iterator& operator++() 58 | { 59 | JL_ASSERT( m_pCurrent ); 60 | 61 | if ( m_pCurrent ) 62 | { 63 | m_pCurrent = m_pCurrent->next; 64 | } 65 | 66 | return *this; 67 | } 68 | 69 | bool operator==( const iterator& other ) const 70 | { 71 | return m_pList == other.m_pList && m_pCurrent == other.m_pCurrent; 72 | } 73 | 74 | bool isValid() const 75 | { 76 | return m_pList != NULL && m_pCurrent != NULL; 77 | } 78 | 79 | private: 80 | friend class DoublyLinkedList; 81 | DoublyLinkedList* m_pList; 82 | Node* m_pCurrent; 83 | }; 84 | 85 | class const_iterator 86 | { 87 | public: 88 | const TObject& operator*() 89 | { 90 | return m_pCurrent->object; 91 | } 92 | 93 | const TObject& operator->() 94 | { 95 | return m_pCurrent->object; 96 | } 97 | 98 | const_iterator& operator--() 99 | { 100 | JL_ASSERT( m_pCurrent ); 101 | 102 | if ( m_pCurrent ) 103 | { 104 | m_pCurrent = m_pCurrent->prev; 105 | } 106 | 107 | return *this; 108 | } 109 | 110 | const_iterator& operator++() 111 | { 112 | JL_ASSERT( m_pCurrent ); 113 | 114 | if ( m_pCurrent ) 115 | { 116 | m_pCurrent = m_pCurrent->next; 117 | } 118 | 119 | return *this; 120 | } 121 | 122 | bool operator==( const const_iterator& other ) const 123 | { 124 | return m_pList == other.m_pList && m_pCurrent == other.m_pCurrent; 125 | } 126 | 127 | bool isValid() const 128 | { 129 | return m_pList != NULL && m_pCurrent != NULL; 130 | } 131 | 132 | private: 133 | friend class DoublyLinkedList; 134 | const DoublyLinkedList* m_pList; 135 | const Node* m_pCurrent; 136 | }; 137 | 138 | ///////////////////// 139 | // Internal interface 140 | ///////////////////// 141 | 142 | private: 143 | Node* CreateNode() 144 | { 145 | Node* pNode = (Node*)m_pNodeAllocator->Alloc( sizeof(Node) ); 146 | 147 | if ( ! pNode ) 148 | { 149 | return NULL; 150 | } 151 | 152 | // Initialize node pointers 153 | pNode->next = NULL; 154 | pNode->prev = NULL; 155 | 156 | return pNode; 157 | } 158 | 159 | /////////////////// 160 | // Public interface 161 | /////////////////// 162 | 163 | public: 164 | 165 | DoublyLinkedList() 166 | { 167 | m_pHead = NULL; 168 | m_pTail = NULL; 169 | m_nObjectCount = 0; 170 | m_pNodeAllocator = NULL; 171 | } 172 | 173 | ~DoublyLinkedList() 174 | { 175 | Clear(); 176 | } 177 | 178 | void Init( ScopedAllocator* pNodeAllocator ) 179 | { 180 | m_pNodeAllocator = pNodeAllocator; 181 | } 182 | 183 | // Returns true if the object was successfully added 184 | Node* Add( const TObject& object ) 185 | { 186 | // Create a node to contain the object. 187 | Node* pNode = CreateNode(); 188 | JL_ASSERT( pNode ); 189 | 190 | if ( ! pNode ) 191 | { 192 | return NULL; 193 | } 194 | 195 | // Place the object in the node. 196 | pNode->object = object; 197 | 198 | // Add node to the end of the list. 199 | if ( m_pTail ) 200 | { 201 | m_pTail->next = pNode; 202 | pNode->prev = m_pTail; 203 | m_pTail = pNode; 204 | } 205 | else 206 | { 207 | JL_ASSERT( ! m_pHead ); 208 | m_pHead = pNode; 209 | m_pTail = pNode; 210 | } 211 | 212 | // Update object count 213 | m_nObjectCount += 1; 214 | 215 | return pNode; 216 | } 217 | 218 | // Returns true if the object was successfully removed. This will only 219 | // remove the first instance of the object. 220 | bool Remove( const TObject& object ) 221 | { 222 | for ( Node* pNode = m_pHead; pNode != NULL; pNode = pNode->next ) 223 | { 224 | if ( pNode->object == object ) 225 | { 226 | return RemoveNode( pNode ); 227 | } 228 | } 229 | 230 | return false; 231 | } 232 | 233 | // Returns true if the object at the iterator position was successfully removed 234 | // This will advance the iterator if the removal was successful. 235 | bool Remove( iterator& i ) 236 | { 237 | JL_ASSERT( i.m_pList == this ); 238 | if ( i.m_pList != this ) 239 | { 240 | return false; 241 | } 242 | 243 | Node* pNext = i.m_pCurrent->next; 244 | if ( RemoveNode(i.m_pCurrent) ) 245 | { 246 | i.m_pCurrent = pNext; 247 | return true; 248 | } 249 | 250 | return false; 251 | } 252 | 253 | // Returns the number of nodes removed from the list. 254 | unsigned RemoveAll( const TObject& object ) 255 | { 256 | const unsigned nSizeBefore = m_nObjectCount; 257 | 258 | Node* pCurrent = NULL; 259 | Node* pNext = m_pHead; 260 | 261 | while ( pNext ) 262 | { 263 | pCurrent = pNext; 264 | pNext = pCurrent->next; 265 | 266 | if ( pCurrent->object == object ) 267 | { 268 | RemoveNode( pCurrent ); 269 | } 270 | } 271 | 272 | JL_ASSERT( nSizeBefore >= m_nObjectCount ); 273 | return nSizeBefore - m_nObjectCount; 274 | } 275 | 276 | unsigned Count() const 277 | { 278 | return m_nObjectCount; 279 | } 280 | 281 | unsigned IsEmpty() const 282 | { 283 | return m_nObjectCount == 0; 284 | } 285 | 286 | void Clear() 287 | { 288 | Node* pCurrent = NULL; 289 | Node* pNext = m_pHead; 290 | 291 | while ( pNext ) 292 | { 293 | pCurrent = pNext; 294 | pNext = pCurrent->next; 295 | 296 | m_pNodeAllocator->Free( pCurrent ); 297 | } 298 | 299 | m_pHead = NULL; 300 | m_pTail = NULL; 301 | m_nObjectCount = 0; 302 | } 303 | 304 | // O(N) random access 305 | TObject& operator[]( unsigned n ) 306 | { 307 | JL_ASSERT( n < m_nObjectCount ); 308 | 309 | unsigned i = 0; 310 | Node* pNode = m_pHead; 311 | 312 | for (;;) 313 | { 314 | JL_ASSERT( pNode ); 315 | 316 | if ( i == n ) 317 | { 318 | return pNode->object; 319 | } 320 | 321 | i += 1; 322 | pNode = pNode->next; 323 | } 324 | } 325 | 326 | const TObject& operator[]( unsigned n ) const 327 | { 328 | JL_ASSERT( n < m_nObjectCount ); 329 | 330 | unsigned i = 0; 331 | const Node* pNode = m_pHead; 332 | 333 | for (;;) 334 | { 335 | JL_ASSERT( pNode ); 336 | 337 | if ( i == n ) 338 | { 339 | return pNode->object; 340 | } 341 | 342 | i += 1; 343 | pNode = pNode->next; 344 | } 345 | } 346 | 347 | // Iterator interface 348 | iterator begin() 349 | { 350 | iterator i; 351 | i.m_pList = this; 352 | i.m_pCurrent = m_pHead; 353 | return i; 354 | } 355 | 356 | const_iterator const_begin() const 357 | { 358 | const_iterator i; 359 | i.m_pList = this; 360 | i.m_pCurrent = m_pHead; 361 | return i; 362 | } 363 | 364 | private: 365 | bool RemoveNode( Node* pNode ) 366 | { 367 | JL_ASSERT( m_nObjectCount ); 368 | if ( ! m_nObjectCount ) 369 | { 370 | return false; 371 | } 372 | 373 | // Re-assign head/tail pointers, if necessary 374 | if ( m_pHead == pNode ) 375 | { 376 | m_pHead = pNode->next; 377 | } 378 | 379 | if ( m_pTail == pNode ) 380 | { 381 | m_pTail = pNode->prev; 382 | } 383 | 384 | // Reassign links between previous/next buckets 385 | if ( pNode->prev ) 386 | { 387 | pNode->prev->next = pNode->next; 388 | } 389 | 390 | if ( pNode->next ) 391 | { 392 | pNode->next->prev = pNode->prev; 393 | } 394 | 395 | // Update object count 396 | m_nObjectCount -= 1; 397 | 398 | // Free node object 399 | m_pNodeAllocator->Free( pNode ); 400 | 401 | return true; 402 | } 403 | 404 | Node* m_pHead; 405 | Node* m_pTail; 406 | unsigned m_nObjectCount; 407 | ScopedAllocator* m_pNodeAllocator; 408 | }; 409 | 410 | } // namespace jl 411 | 412 | #endif // ! defined( _JL_DOUBLY_LINKED_LIST_H_ ) -------------------------------------------------------------------------------- /src/ObjectPoolTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ObjectPool.h" 7 | 8 | using namespace jl; 9 | 10 | // This is a unit test of the various object pool classes. 11 | namespace 12 | { 13 | // TestObject instances will be allocated by our object pool allocators. 14 | // These are simple wrappers around string pointers. We'll test to make sure 15 | // the string pointers are the same after subsequent allocations to the same 16 | // pool. 17 | class TestObject 18 | { 19 | public: 20 | TestObject( const char* p = NULL ) : m_pContents(p) {} 21 | const char* GetContents() const { return m_pContents; } 22 | 23 | private: 24 | const char* m_pContents; 25 | }; 26 | 27 | // TestState instances wrap around allocated TestObject instances. They take 28 | // a newly-allocated TestObject and memoize its contents. After the 29 | // allocator state changes, use IsValid() to ensure that allocated object 30 | // state has not been clobbered. 31 | class TestState 32 | { 33 | public: 34 | TestState() : m_pContentsCopy(NULL), m_pObject(NULL) {} 35 | 36 | void SetObject( TestObject* pObject = NULL ) 37 | { 38 | m_pObject = pObject; 39 | m_pContentsCopy = m_pObject ? m_pObject->GetContents() : NULL; 40 | } 41 | 42 | TestObject* GetObject() const { return m_pObject; } 43 | 44 | bool IsValid() const 45 | { 46 | return m_pObject == NULL || m_pObject->GetContents() == m_pContentsCopy; 47 | } 48 | 49 | template 50 | static bool IsValidArray( const TestState(&pArray)[_ArraySize] ) 51 | { 52 | for ( unsigned i = 0; i < _ArraySize; ++i ) 53 | { 54 | if ( ! pArray[i].IsValid() ) 55 | { 56 | return false; 57 | } 58 | } 59 | 60 | return true; 61 | } 62 | 63 | private: 64 | const char* m_pContentsCopy; 65 | TestObject* m_pObject; 66 | }; 67 | 68 | // Allocator for PreallocatedObjectPool 69 | template 70 | class PreallocatedPoolFactory 71 | { 72 | public: 73 | enum 74 | { 75 | eStride = _Stride, 76 | eCapacity = _Capacity 77 | }; 78 | 79 | typedef PreallocatedObjectPool InternalObjectPool; 80 | 81 | static InternalObjectPool* Create() 82 | { 83 | void* pBuffer = new unsigned char[ _Stride * _Capacity ]; 84 | InternalObjectPool* pPool = new InternalObjectPool(); 85 | pPool->Init( pBuffer, _Capacity, _Stride, true ); 86 | return pPool; 87 | } 88 | }; 89 | 90 | // Allocator for StaticObjectPool 91 | template 92 | class StaticPoolFactory 93 | { 94 | public: 95 | enum 96 | { 97 | eStride = _Stride, 98 | eCapacity = _Capacity 99 | }; 100 | 101 | typedef StaticObjectPool<_Stride, _Capacity> InternalObjectPool; 102 | 103 | static InternalObjectPool* Create() 104 | { 105 | return new InternalObjectPool(); 106 | } 107 | }; 108 | 109 | const char* g_ppSampleContents[] = { 110 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", 111 | "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" 112 | }; 113 | 114 | template 115 | void PoolTest() 116 | { 117 | typedef typename _TAllocatorFactory::InternalObjectPool PoolType; 118 | PoolType* pPool; 119 | TestState pTestState[_PoolSize]; 120 | 121 | printf( "Creating pool...\n" ); 122 | pPool = _TAllocatorFactory::Create(); 123 | 124 | for ( unsigned nTrialSize = 1; nTrialSize <= _PoolSize; ++nTrialSize ) 125 | { 126 | printf( "Testing %u allocations on a pool with size %u\n", nTrialSize, _PoolSize ); 127 | 128 | // Ensure that the internal allocation count is zero 129 | assert( 0 == pPool->CountAllocations() ); 130 | 131 | // Ensure that the internal allocation count corresponds to the free list size 132 | assert( pPool->CountAllocations() == pPool->GetCapacity() - ObjectPool::FreeListSize(pPool->GetFreeListHead()) ); 133 | 134 | // Ensure that allocations haven't been corrupted 135 | assert( TestState::IsValidArray(pTestState) ); 136 | 137 | // Initial allocations 138 | for ( unsigned i = 0; i < nTrialSize; ++i ) 139 | { 140 | pTestState[i].SetObject( 141 | new( pPool->Alloc() ) TestObject( g_ppSampleContents[i] ) 142 | ); 143 | 144 | // Validations 145 | { 146 | // Ensure that internal allocation count is accurate 147 | assert( i + 1 == pPool->CountAllocations() ); 148 | 149 | // Ensure that free list size corresponds to current number of allocations 150 | assert( pPool->CountAllocations() == pPool->GetCapacity() - ObjectPool::FreeListSize(pPool->GetFreeListHead()) ); 151 | 152 | // Ensure that allocations haven't been corrupted 153 | assert( TestState::IsValidArray(pTestState) ); 154 | } 155 | } 156 | 157 | // Random replacements 158 | const unsigned nReplacements = nTrialSize; 159 | for ( unsigned i = 0; i < nReplacements; ++i ) 160 | { 161 | const int nReplacementIndex = rand() % nTrialSize; 162 | 163 | // Free the object 164 | pTestState[nReplacementIndex].GetObject()->~TestObject(); 165 | pPool->Free( static_cast(pTestState[nReplacementIndex].GetObject()) ); 166 | pTestState[nReplacementIndex].SetObject(); 167 | 168 | // Validations 169 | { 170 | // Ensure that the old internal allocation count corresponds to the current test size, minus one 171 | assert( nTrialSize - 1 == pPool->CountAllocations() ); 172 | 173 | // Ensure that the free list size corresponds to the number of allocations 174 | assert( pPool->CountAllocations() == pPool->GetCapacity() - ObjectPool::FreeListSize(pPool->GetFreeListHead()) ); 175 | 176 | // Ensure that allocations haven't been corrupted 177 | assert( TestState::IsValidArray(pTestState) ); 178 | } 179 | 180 | // Create a new object 181 | const char* pReplacementValue = g_ppSampleContents[ rand() % JL_ARRAY_SIZE(g_ppSampleContents) ]; 182 | pTestState[nReplacementIndex].SetObject( 183 | new( pPool->Alloc() ) TestObject( pReplacementValue ) 184 | ); 185 | 186 | // Validations 187 | { 188 | // Ensure that the internal allocation count corresponds to the current test size 189 | assert( nTrialSize == pPool->CountAllocations() ); 190 | 191 | // Ensure that the free list size corresponds to the number of allocations 192 | assert( pPool->CountAllocations() == pPool->GetCapacity() - ObjectPool::FreeListSize(pPool->GetFreeListHead()) ); 193 | 194 | // Ensure that allocations haven't been corrupted 195 | assert( TestState::IsValidArray(pTestState) ); 196 | } 197 | } 198 | 199 | // Free all allocations 200 | for ( unsigned i = 0; i < nTrialSize; ++i ) 201 | { 202 | // Cache the old internal allocation count 203 | const int nPreFreeAllocations = pPool->CountAllocations(); 204 | 205 | // Free the object 206 | pTestState[i].GetObject()->~TestObject(); 207 | pPool->Free( static_cast(pTestState[i].GetObject()) ); 208 | pTestState[i].SetObject(); 209 | 210 | // Validations 211 | { 212 | // Ensure that the old internal allocation count corresponds to the current allocation count 213 | assert( nPreFreeAllocations - 1 == pPool->CountAllocations() ); 214 | 215 | // Ensure that the free list size corresponds to the number of allocations 216 | assert( pPool->CountAllocations() == pPool->GetCapacity() - ObjectPool::FreeListSize(pPool->GetFreeListHead()) ); 217 | 218 | // Ensure that allocations haven't been corrupted 219 | assert( TestState::IsValidArray(pTestState) ); 220 | } 221 | } 222 | 223 | // Final validations 224 | { 225 | // Ensure that the internal allocation count is zero 226 | assert( 0 == pPool->CountAllocations() ); 227 | 228 | // Ensure that the internal allocation count corresponds to the free list size 229 | assert( pPool->CountAllocations() == pPool->GetCapacity() - ObjectPool::FreeListSize(pPool->GetFreeListHead()) ); 230 | 231 | // Ensure that allocations haven't been corrupted 232 | assert( TestState::IsValidArray(pTestState) ); 233 | } 234 | } // Increment trial size 235 | } 236 | } 237 | 238 | void ObjectPoolTest() 239 | { 240 | enum { ePoolCapacity = 1000 }; 241 | 242 | printf("Testing PreallocatedObjectPool...\n"); 243 | typedef PreallocatedPoolFactory PreallocatedPoolTestFactory; 244 | PoolTest(); 245 | 246 | printf("\nTesting StaticObjectPool...\n"); 247 | typedef StaticPoolFactory StaticPoolTestFactory; 248 | PoolTest(); 249 | } 250 | -------------------------------------------------------------------------------- /util/signal_class_template.erb: -------------------------------------------------------------------------------- 1 | /** 2 | * Signal<%= arg_count %>: signals with <%= arg_count %> <%= arg_count == 1 ? 'argument' : 'arguments' %> 3 | */ 4 | template< <%= template_signature %> > 5 | class Signal<%= arg_count %> : public SignalBase 6 | { 7 | public: 8 | typedef fastdelegate::FastDelegate<%= arg_count %>< <%= arg_type_list %> > Delegate; 9 | 10 | struct Connection 11 | { 12 | Delegate d; 13 | SignalObserver* pObserver; 14 | }; 15 | 16 | typedef DoublyLinkedList ConnectionList; 17 | 18 | enum { eAllocationSize = sizeof(typename ConnectionList::Node) }; 19 | 20 | private: 21 | typedef typename ConnectionList::iterator ConnectionIter; 22 | typedef typename ConnectionList::const_iterator ConnectionConstIter; 23 | 24 | ConnectionList m_oConnections; 25 | 26 | public: 27 | Signal<%= arg_count %>() { SetAllocator( s_pCommonAllocator ); } 28 | Signal<%= arg_count %>( ScopedAllocator* pAllocator ) { SetAllocator( pAllocator ); } 29 | 30 | virtual ~Signal<%= arg_count %>() 31 | { 32 | JL_SIGNAL_LOG( "Destroying Signal<%= arg_count %> %p\n", this ); 33 | DisconnectAll(); 34 | } 35 | 36 | void SetAllocator( ScopedAllocator* pAllocator ) { m_oConnections.Init( pAllocator ); } 37 | unsigned CountConnections() const { return m_oConnections.Count(); } 38 | 39 | // Connects non-instance functions. 40 | void Connect( void (*fpFunction)(<%= arg_type_list %>) ) 41 | { 42 | JL_SIGNAL_DOUBLE_CONNECTED_FUNCTION_ASSERT( fpFunction ); 43 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p connection to non-instance function %p", this, BruteForceCast(fpFunction) ); 44 | 45 | Connection c = { Delegate(fpFunction), NULL }; 46 | const bool bAdded = m_oConnections.Add( c ); 47 | JL_ASSERT( bAdded ); 48 | } 49 | 50 | // Connects instance methods. Class X should be equal to Y, or an ancestor type. 51 | template< class X, class Y > 52 | void Connect( Y* pObject, void (X::*fpMethod)(<%= arg_type_list %>) ) 53 | { 54 | if ( ! pObject ) 55 | { 56 | return; 57 | } 58 | 59 | JL_SIGNAL_DOUBLE_CONNECTED_INSTANCE_METHOD_ASSERT( pObject, fpMethod ); 60 | SignalObserver* pObserver = static_cast( pObject ); 61 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p connecting to Observer %p (object %p, method %p)\n", this, pObserver, pObject, BruteForceCast(fpMethod) ); 62 | 63 | Connection c = { Delegate(pObject, fpMethod), pObserver }; 64 | const bool bAdded = m_oConnections.Add( c ); 65 | JL_ASSERT( bAdded ); 66 | NotifyObserverConnect( pObserver ); 67 | } 68 | 69 | // Connects const instance methods. Class X should be equal to Y, or an ancestor type. 70 | template< class X, class Y > 71 | void Connect( Y* pObject, void (X::*fpMethod)(<%= arg_type_list %>) const ) 72 | { 73 | if ( ! pObject ) 74 | { 75 | return; 76 | } 77 | 78 | JL_SIGNAL_DOUBLE_CONNECTED_INSTANCE_METHOD_ASSERT( pObject, fpMethod ); 79 | SignalObserver* pObserver = static_cast( pObject ); 80 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p connecting to Observer %p (object %p, method %p)\n", this, pObserver, pObject, BruteForceCast(fpMethod) ); 81 | 82 | Connection c = { Delegate(pObject, fpMethod), pObserver }; 83 | const bool bAdded = m_oConnections.Add( c ); 84 | JL_ASSERT( bAdded ); 85 | NotifyObserverConnect( pObserver ); 86 | } 87 | 88 | // Returns true if the given observer and non-instance function are connected to this signal. 89 | bool IsConnected( void (*fpFunction)(<%= arg_type_list %>) ) const 90 | { 91 | return IsConnected( Delegate(fpFunction) ); 92 | } 93 | 94 | // Returns true if the given observer and instance method are connected to this signal. 95 | template< class X, class Y > 96 | bool IsConnected( Y* pObject, void (X::*fpMethod)(<%= arg_type_list %>) ) const 97 | { 98 | return IsConnected( Delegate(pObject, fpMethod) ); 99 | } 100 | 101 | // Returns true if the given observer and const instance method are connected to this signal. 102 | template< class X, class Y > 103 | bool IsConnected( Y* pObject, void (X::*fpMethod)(<%= arg_type_list %>) const ) const 104 | { 105 | return IsConnected( Delegate(pObject, fpMethod) ); 106 | } 107 | 108 | void Emit( <%= arg_signature %> ) const 109 | { 110 | for ( ConnectionConstIter i = m_oConnections.const_begin(); i.isValid(); ++i ) 111 | { 112 | (*i).d( <%= arg_list %> ); 113 | } 114 | } 115 | 116 | void operator()( <%= arg_signature %> ) const { Emit( <%= arg_list %> ); } 117 | 118 | // Disconnects a non-instance method. 119 | void Disconnect( void (*fpFunction)(<%= arg_type_list %>) ) 120 | { 121 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p removing connections to non-instance method %p\n", this, BruteForceCast(fpFunction) ); 122 | const Delegate d(fpFunction); 123 | 124 | for ( ConnectionIter i = m_oConnections.begin(); i.isValid(); ) 125 | { 126 | if ( (*i).d == d ) 127 | { 128 | JL_ASSERT( (*i).pObserver == NULL ); 129 | JL_SIGNAL_LOG( "\tRemoving connection...\n" ); 130 | m_oConnections.Remove( i ); 131 | } 132 | else 133 | { 134 | ++i; 135 | } 136 | } 137 | } 138 | 139 | // Disconnects instance methods. Class X should be equal to Y, or an ancestor type. 140 | template< class X, class Y > 141 | void Disconnect( Y* pObject, void (X::*fpMethod)(<%= arg_type_list %>) ) 142 | { 143 | if ( ! pObject ) 144 | { 145 | return; 146 | } 147 | 148 | SignalObserver* pObserver = static_cast( pObject ); 149 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p removing connections to Observer %p, instance method (object %p, method %p)\n", this, pObserver, pObject, BruteForceCast(fpMethod) ); 150 | DisconnectObserverDelegate( pObserver, Delegate(pObject, fpMethod) ); 151 | } 152 | 153 | // Disconnects const instance methods. Class X should be equal to Y, or an ancestor type. 154 | template< class X, class Y > 155 | void Disconnect( Y* pObject, void (X::*fpMethod)(<%= arg_type_list %>) const ) 156 | { 157 | if ( ! pObject ) 158 | { 159 | return; 160 | } 161 | 162 | SignalObserver* pObserver = static_cast( pObject ); 163 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p removing connections to Observer %p, const instance method (object %p, method %p)\n", this, pObserver, pObject, BruteForceCast(fpMethod) ); 164 | DisconnectObserverDelegate( pObserver, Delegate(pObject, fpMethod) ); 165 | } 166 | 167 | // Disconnects all connected instance methods from a single observer. Calls NotifyObserverDisconnect() 168 | // if any disconnections are made. 169 | void Disconnect( SignalObserver* pObserver ) 170 | { 171 | if ( ! pObserver ) 172 | { 173 | return; 174 | } 175 | 176 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p removing all connections to Observer %p\n", this, pObserver ); 177 | unsigned nDisconnections = 0; 178 | 179 | for ( ConnectionIter i = m_oConnections.begin(); i.isValid(); ) 180 | { 181 | if ( (*i).pObserver == pObserver ) 182 | { 183 | JL_SIGNAL_LOG( "\tRemoving connection to observer\n" ); 184 | m_oConnections.Remove( i ); // advances iterator 185 | ++nDisconnections; 186 | } 187 | else 188 | { 189 | ++i; 190 | } 191 | } 192 | 193 | if ( nDisconnections > 0 ) 194 | { 195 | NotifyObserverDisconnect( pObserver ); 196 | } 197 | } 198 | 199 | void DisconnectAll() 200 | { 201 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p disconnecting all observers\n", this ); 202 | 203 | for ( ConnectionIter i = m_oConnections.begin(); i.isValid(); ++i ) 204 | { 205 | SignalObserver* pObserver = (*i).pObserver; 206 | 207 | //HACK - call this each time we encounter a valid observer pointer. This means 208 | // this will be called repeatedly for observers that are connected multiple times 209 | // to this signal. 210 | if ( pObserver ) 211 | { 212 | NotifyObserverDisconnect( pObserver ); 213 | } 214 | } 215 | 216 | m_oConnections.Clear(); 217 | } 218 | 219 | private: 220 | bool IsConnected( const Delegate& d ) const 221 | { 222 | for ( ConnectionConstIter i = m_oConnections.const_begin(); i.isValid(); ++i ) 223 | { 224 | if ( (*i).d == d ) 225 | { 226 | return true; 227 | } 228 | } 229 | 230 | return false; 231 | } 232 | 233 | // Disconnects a specific slot on an observer. Calls NotifyObserverDisconnect() if 234 | // the observer is completely disconnected from this signal. 235 | void DisconnectObserverDelegate( SignalObserver* pObserver, const Delegate& d ) 236 | { 237 | unsigned nDisconnections = 0; // number of disconnections. This is 0 or 1 unless you connected the same slot twice. 238 | unsigned nObserverConnectionCount = 0; // number of times the observer is connected to this signal 239 | 240 | for ( ConnectionIter i = m_oConnections.begin(); i.isValid(); ) 241 | { 242 | if ( (*i).d == d ) 243 | { 244 | JL_ASSERT( (*i).pObserver == pObserver ); 245 | JL_SIGNAL_LOG( "\tRemoving connection...\n" ); 246 | m_oConnections.Remove( i ); // advances iterator 247 | ++nDisconnections; 248 | } 249 | else 250 | { 251 | if ( (*i).pObserver == pObserver ) 252 | { 253 | ++nObserverConnectionCount; 254 | } 255 | 256 | ++i; 257 | } 258 | } 259 | 260 | if ( nDisconnections > 0 && nObserverConnectionCount == 0 ) 261 | { 262 | JL_SIGNAL_LOG( "\tCompletely disconnected observer %p!", pObserver ); 263 | NotifyObserverDisconnect( pObserver ); 264 | } 265 | } 266 | 267 | void OnObserverDisconnect( SignalObserver* pObserver ) 268 | { 269 | JL_SIGNAL_LOG( "Signal<%= arg_count %> %p received disconnect message from observer %p\n", this, pObserver ); 270 | 271 | for ( ConnectionIter i = m_oConnections.begin(); i.isValid(); ) 272 | { 273 | if ( (*i).pObserver == pObserver ) 274 | { 275 | JL_SIGNAL_LOG( "\tRemoving connection to observer\n" ); 276 | m_oConnections.Remove( i ); 277 | } 278 | else 279 | { 280 | ++i; 281 | } 282 | } 283 | } 284 | }; 285 | 286 | #ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX 287 | 288 | /** 289 | * Signal<[<%= arg_count %> <%= arg_count == 1 ? 'argument' : 'arguments' %>]>: wrapper class for Signal<%= arg_count %> 290 | * This wrapper template, in conjunction with the "JL_SIGNAL()" macro, will allow you to ignore the argument count in the signal typename. 291 | */ 292 | template< typename TUnused<%= ', ' + template_signature if arg_count > 0 %> > 293 | class Signal< TUnused(<%= arg_type_list %>) > : public Signal<%= arg_count %>< <%= arg_type_list %> > 294 | { 295 | public: 296 | typedef Signal<%= arg_count %>< <%= arg_type_list %> > TParent; 297 | Signal() {} 298 | Signal( ScopedAllocator* pNodeAllocator ) : TParent(pNodeAllocator) {} 299 | void operator=( const TParent& other ) { *static_cast(this) = other; } 300 | }; 301 | 302 | #endif // defined( FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX ) -------------------------------------------------------------------------------- /prj/xcode/jl_signal/jl_signal.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3752E28C15B8F6E9005B47D7 /* DoublyLinkedListTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3752E26E15B8F64E005B47D7 /* DoublyLinkedListTest.cpp */; }; 11 | 3752E28D15B8F6E9005B47D7 /* ObjectPool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3752E27015B8F64E005B47D7 /* ObjectPool.cpp */; }; 12 | 3752E28E15B8F6E9005B47D7 /* ObjectPoolTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3752E27315B8F64E005B47D7 /* ObjectPoolTest.cpp */; }; 13 | 3752E28F15B8F6E9005B47D7 /* SignalBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3752E27615B8F64E005B47D7 /* SignalBase.cpp */; }; 14 | 3752E29015B8F6E9005B47D7 /* SignalTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3752E27A15B8F64E005B47D7 /* SignalTest.cpp */; }; 15 | 3752E29115B8F6E9005B47D7 /* testMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3752E27B15B8F64E005B47D7 /* testMain.cpp */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 3752E27F15B8F66C005B47D7 /* CopyFiles */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = /usr/share/man/man1/; 23 | dstSubfolderSpec = 0; 24 | files = ( 25 | ); 26 | runOnlyForDeploymentPostprocessing = 1; 27 | }; 28 | /* End PBXCopyFilesBuildPhase section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 3752E26D15B8F64E005B47D7 /* DoublyLinkedList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = DoublyLinkedList.h; path = ../../../src/DoublyLinkedList.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 32 | 3752E26E15B8F64E005B47D7 /* DoublyLinkedListTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; name = DoublyLinkedListTest.cpp; path = ../../../src/DoublyLinkedListTest.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; 33 | 3752E26F15B8F64E005B47D7 /* FastDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FastDelegate.h; path = ../../../src/FastDelegate.h; sourceTree = ""; }; 34 | 3752E27015B8F64E005B47D7 /* ObjectPool.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 2; name = ObjectPool.cpp; path = ../../../src/ObjectPool.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; 35 | 3752E27115B8F64E005B47D7 /* ObjectPool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 2; name = ObjectPool.h; path = ../../../src/ObjectPool.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 36 | 3752E27215B8F64E005B47D7 /* ObjectPoolScopedAllocator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 2; name = ObjectPoolScopedAllocator.h; path = ../../../src/ObjectPoolScopedAllocator.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 37 | 3752E27315B8F64E005B47D7 /* ObjectPoolTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ObjectPoolTest.cpp; path = ../../../src/ObjectPoolTest.cpp; sourceTree = ""; }; 38 | 3752E27415B8F64E005B47D7 /* ScopedAllocator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ScopedAllocator.h; path = ../../../src/ScopedAllocator.h; sourceTree = ""; }; 39 | 3752E27515B8F64E005B47D7 /* Signal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Signal.h; path = ../../../src/Signal.h; sourceTree = ""; }; 40 | 3752E27615B8F64E005B47D7 /* SignalBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SignalBase.cpp; path = ../../../src/SignalBase.cpp; sourceTree = ""; }; 41 | 3752E27715B8F64E005B47D7 /* SignalBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SignalBase.h; path = ../../../src/SignalBase.h; sourceTree = ""; }; 42 | 3752E27815B8F64E005B47D7 /* StaticSignalConnectionAllocators.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = StaticSignalConnectionAllocators.h; path = ../../../src/StaticSignalConnectionAllocators.h; sourceTree = ""; }; 43 | 3752E27915B8F64E005B47D7 /* SignalDefinitions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SignalDefinitions.h; path = ../../../src/SignalDefinitions.h; sourceTree = ""; }; 44 | 3752E27A15B8F64E005B47D7 /* SignalTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SignalTest.cpp; path = ../../../src/SignalTest.cpp; sourceTree = ""; }; 45 | 3752E27B15B8F64E005B47D7 /* testMain.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = testMain.cpp; path = ../../../src/testMain.cpp; sourceTree = ""; }; 46 | 3752E27C15B8F64E005B47D7 /* Utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = ../../../src/Utils.h; sourceTree = ""; }; 47 | 3752E28115B8F66C005B47D7 /* jl_signal */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jl_signal; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 37F00C2415BB050E00C6929E /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.md; path = ../../../README.md; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 3752E27E15B8F66C005B47D7 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 3752E26415B8F63F005B47D7 = { 63 | isa = PBXGroup; 64 | children = ( 65 | 37F00C2215BB050000C6929E /* doc */, 66 | 3752E26D15B8F64E005B47D7 /* DoublyLinkedList.h */, 67 | 3752E26E15B8F64E005B47D7 /* DoublyLinkedListTest.cpp */, 68 | 3752E26F15B8F64E005B47D7 /* FastDelegate.h */, 69 | 3752E27015B8F64E005B47D7 /* ObjectPool.cpp */, 70 | 3752E27115B8F64E005B47D7 /* ObjectPool.h */, 71 | 3752E27215B8F64E005B47D7 /* ObjectPoolScopedAllocator.h */, 72 | 3752E27315B8F64E005B47D7 /* ObjectPoolTest.cpp */, 73 | 3752E27415B8F64E005B47D7 /* ScopedAllocator.h */, 74 | 3752E27515B8F64E005B47D7 /* Signal.h */, 75 | 3752E27615B8F64E005B47D7 /* SignalBase.cpp */, 76 | 3752E27715B8F64E005B47D7 /* SignalBase.h */, 77 | 3752E27815B8F64E005B47D7 /* StaticSignalConnectionAllocators.h */, 78 | 3752E27915B8F64E005B47D7 /* SignalDefinitions.h */, 79 | 3752E27A15B8F64E005B47D7 /* SignalTest.cpp */, 80 | 3752E27B15B8F64E005B47D7 /* testMain.cpp */, 81 | 3752E27C15B8F64E005B47D7 /* Utils.h */, 82 | 3752E28215B8F66C005B47D7 /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 3752E28215B8F66C005B47D7 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 3752E28115B8F66C005B47D7 /* jl_signal */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | 37F00C2215BB050000C6929E /* doc */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 37F00C2415BB050E00C6929E /* README.md */, 98 | ); 99 | name = doc; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | 3752E28015B8F66C005B47D7 /* jl_signal */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = 3752E28915B8F66C005B47D7 /* Build configuration list for PBXNativeTarget "jl_signal" */; 108 | buildPhases = ( 109 | 3752E27D15B8F66C005B47D7 /* Sources */, 110 | 3752E27E15B8F66C005B47D7 /* Frameworks */, 111 | 3752E27F15B8F66C005B47D7 /* CopyFiles */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = jl_signal; 118 | productName = jl_signal; 119 | productReference = 3752E28115B8F66C005B47D7 /* jl_signal */; 120 | productType = "com.apple.product-type.tool"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 3752E26615B8F63F005B47D7 /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastUpgradeCheck = 0430; 129 | }; 130 | buildConfigurationList = 3752E26915B8F63F005B47D7 /* Build configuration list for PBXProject "jl_signal" */; 131 | compatibilityVersion = "Xcode 3.2"; 132 | developmentRegion = English; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | ); 137 | mainGroup = 3752E26415B8F63F005B47D7; 138 | productRefGroup = 3752E28215B8F66C005B47D7 /* Products */; 139 | projectDirPath = ""; 140 | projectRoot = ""; 141 | targets = ( 142 | 3752E28015B8F66C005B47D7 /* jl_signal */, 143 | ); 144 | }; 145 | /* End PBXProject section */ 146 | 147 | /* Begin PBXSourcesBuildPhase section */ 148 | 3752E27D15B8F66C005B47D7 /* Sources */ = { 149 | isa = PBXSourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | 3752E28C15B8F6E9005B47D7 /* DoublyLinkedListTest.cpp in Sources */, 153 | 3752E28D15B8F6E9005B47D7 /* ObjectPool.cpp in Sources */, 154 | 3752E28E15B8F6E9005B47D7 /* ObjectPoolTest.cpp in Sources */, 155 | 3752E28F15B8F6E9005B47D7 /* SignalBase.cpp in Sources */, 156 | 3752E29015B8F6E9005B47D7 /* SignalTest.cpp in Sources */, 157 | 3752E29115B8F6E9005B47D7 /* testMain.cpp in Sources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXSourcesBuildPhase section */ 162 | 163 | /* Begin XCBuildConfiguration section */ 164 | 3752E26B15B8F63F005B47D7 /* Debug */ = { 165 | isa = XCBuildConfiguration; 166 | buildSettings = { 167 | }; 168 | name = Debug; 169 | }; 170 | 3752E26C15B8F63F005B47D7 /* Release */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | }; 174 | name = Release; 175 | }; 176 | 3752E28A15B8F66C005B47D7 /* Debug */ = { 177 | isa = XCBuildConfiguration; 178 | buildSettings = { 179 | ALWAYS_SEARCH_USER_PATHS = NO; 180 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 181 | COPY_PHASE_STRIP = NO; 182 | GCC_C_LANGUAGE_STANDARD = gnu99; 183 | GCC_DYNAMIC_NO_PIC = NO; 184 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 185 | GCC_OPTIMIZATION_LEVEL = 0; 186 | GCC_PREPROCESSOR_DEFINITIONS = ( 187 | "DEBUG=1", 188 | "$(inherited)", 189 | ); 190 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 191 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 192 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 193 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 194 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 195 | GCC_WARN_UNUSED_VARIABLE = YES; 196 | MACOSX_DEPLOYMENT_TARGET = 10.7; 197 | ONLY_ACTIVE_ARCH = YES; 198 | PRODUCT_NAME = "$(TARGET_NAME)"; 199 | SDKROOT = macosx; 200 | }; 201 | name = Debug; 202 | }; 203 | 3752E28B15B8F66C005B47D7 /* Release */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 208 | COPY_PHASE_STRIP = YES; 209 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 210 | GCC_C_LANGUAGE_STANDARD = gnu99; 211 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 212 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 215 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 216 | GCC_WARN_UNUSED_VARIABLE = YES; 217 | MACOSX_DEPLOYMENT_TARGET = 10.7; 218 | ONLY_ACTIVE_ARCH = NO; 219 | PRODUCT_NAME = "$(TARGET_NAME)"; 220 | SDKROOT = macosx; 221 | }; 222 | name = Release; 223 | }; 224 | /* End XCBuildConfiguration section */ 225 | 226 | /* Begin XCConfigurationList section */ 227 | 3752E26915B8F63F005B47D7 /* Build configuration list for PBXProject "jl_signal" */ = { 228 | isa = XCConfigurationList; 229 | buildConfigurations = ( 230 | 3752E26B15B8F63F005B47D7 /* Debug */, 231 | 3752E26C15B8F63F005B47D7 /* Release */, 232 | ); 233 | defaultConfigurationIsVisible = 0; 234 | defaultConfigurationName = Release; 235 | }; 236 | 3752E28915B8F66C005B47D7 /* Build configuration list for PBXNativeTarget "jl_signal" */ = { 237 | isa = XCConfigurationList; 238 | buildConfigurations = ( 239 | 3752E28A15B8F66C005B47D7 /* Debug */, 240 | 3752E28B15B8F66C005B47D7 /* Release */, 241 | ); 242 | defaultConfigurationIsVisible = 0; 243 | defaultConfigurationName = Release; 244 | }; 245 | /* End XCConfigurationList section */ 246 | }; 247 | rootObject = 3752E26615B8F63F005B47D7 /* Project object */; 248 | } 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jl_signal 2 | ========= 3 | 4 | For all your [Observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) needs, a reasonably fast and tidy C++ implementation of signals & slots. 5 | 6 | - For a discussion on my design decisions, [please see this article](https://jeffomatic.github.io/2012/08/06/jl_signal/). 7 | - For an idea of how this library performs to its peers (hint: it does pretty well!), see [this benchmark](https://code.google.com/p/nano-signal-slot/wiki/Performance) or [this benchmark](http://tieba.baidu.com/p/3148762621?see_lz=1). 8 | 9 | [There](http://doc.trolltech.com/signalsandslots.html) [are](http://www.boost.org/libs/signals/) [lots](http://sigslot.sourceforge.net/) [and](http://libsigc.sourceforge.net/) [lots](https://github.com/pbhogan/Signals) of C++ signals & slots systems out there. This one gives you the following: 10 | 11 | #### Simple API 12 | 13 | The library has a minimal initialization step, but after that, it's little more than calling `Connect()` and `Emit()` on your signals and observer objects. Use of template syntax and preprocessor macros in the API is kept to a minimum. 14 | 15 | #### No heap allocation 16 | 17 | By default, the internal allocation system bypasses the heap completely, so you don't need to worry about slow allocations or memory fragmentaton. 18 | 19 | #### Automatic signal disconnection 20 | 21 | In order to prevent dangling pointers, the system will automatically break connections between signals and observers when the observer objects go out of scope. This saves from you the considerable headache of having to disconnect your observers manually. 22 | 23 | #### Fast dispatch from signals to observers 24 | 25 | Signals are implemented with Don Clugston's [FastDelegate](http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible) library, which can execute arbitrary callbacks as fast as is theoretically possible. The library is famous for its non-standard under-the-hood hacks, but [should work](#compatibility) for most mainstream modern compilers. 26 | 27 | #### No external dependencies 28 | 29 | The library is mostly self-contained, with minimal dependencies on the standard library. The only external dependency, `FastDelegate.h`, is bundled with the project source. 30 | 31 | ##### Some caveats: 32 | 33 | - Signals can only connect to functions with a `void` return type. 34 | - The classes in this library are not thread-safe. 35 | - This library does require inheritance for observers, but it's almost completely unobtrusive. 36 | 37 | ### What exactly does this do? 38 | 39 | A **signal** is an object that can invoke an arbitrary list of connected functions. These functions are commonly referred to as **slots**, although the `jl_signal` API does not use the term explicitly. Slot functions are **connected** to a signal, and when you **emit** the signal, each connected slot function will be called. Under the hood, it's not much more than iterable containers of function pointers. 40 | 41 | Signals aren't very useful unless you can pass arguments when you're triggering them. The nice thing about `jl_signal` is that you can use native function syntax to specify your slot parameters, rather than wrapping the parameters in a specialized, inflexible `struct`, as is the case with many C/C++ message-passing or event listener systems. 42 | 43 | Usage example 44 | ------------- 45 | 46 | ```c++ 47 | #include // for output 48 | 49 | #include "Signal.h" // base library 50 | #include "StaticSignalConnectionAllocators.h" // some default allocators for library initialization 51 | 52 | // A class whose instances will receive signals. 53 | class Orc : public jl::SignalObserver 54 | { 55 | public: 56 | // This method will be used as a SLOT. 57 | void Retort() { std::cout << "GRUMBLE GRUMBLE GRUMBLE...\n"; } 58 | 59 | // Another slot, this one taking parameters. 60 | void TakeDamage( float fDamage ) 61 | { 62 | if (fDamage >= 20.f) std::cout << "Orc down!\n"; 63 | } 64 | }; 65 | 66 | // Another class whose instances receive signals. 67 | class HipsterBystander : public jl::SignalObserver 68 | { 69 | public: 70 | void SmugComment() 71 | { 72 | std::cout << "Whatever, I think the first movie was better.\n"; 73 | } 74 | }; 75 | 76 | // Another class whose instances receive signals. 77 | class Prop : public jl::SignalObserver 78 | { 79 | public: 80 | void TakeDamage( float fDamage ) 81 | { 82 | if (fDamage >= 10.f) std::cout << "SMASH!\n"; 83 | } 84 | }; 85 | 86 | // A class who instances broadcast signals. 87 | class Wizard 88 | { 89 | public: 90 | // Declare signal that connects to functions that take no args. 91 | JL_SIGNAL() BattleCrySignal; 92 | 93 | // Declare a signal that connects to functions with one float arg. 94 | JL_SIGNAL( float ) MysticalShotgunSignal; 95 | 96 | void BattleCry() 97 | { 98 | std::cout << "This is my boomstick!\n"; 99 | BattleCrySignal.Emit(); 100 | }; 101 | 102 | void FireMysticalShotgun( float fDamage ) 103 | { 104 | std::cout << "BLAM!\n"; 105 | MysticalShotgunSignal.Emit( fDamage ); 106 | } 107 | }; 108 | 109 | int main() 110 | { 111 | // Instantiate some allocators used by the signal system. 112 | enum { eMaxConnections = 50 }; 113 | jl::StaticSignalConnectionAllocator< eMaxConnections > oSignalConnectionAllocator; 114 | jl::StaticObserverConnectionAllocator< eMaxConnections > oObserverConnectionAllocator; 115 | 116 | // Initialize the signal system with our allocators 117 | jl::SignalBase::SetCommonConnectionAllocator( &oSignalConnectionAllocator ); 118 | jl::SignalObserver::SetCommonConnectionAllocator( &oObserverConnectionAllocator ); 119 | 120 | // Instantiate our entities. 121 | Orc rosencrantz, guildenstern; 122 | HipsterBystander chad; 123 | Prop chair; 124 | Wizard merlin; 125 | 126 | // Orcs and hipster bystanders respond to battle cries 127 | merlin.BattleCrySignal.Connect( &rosencrantz, &Orc::Retort ); 128 | merlin.BattleCrySignal.Connect( &guildenstern, &Orc::Retort ); 129 | merlin.BattleCrySignal.Connect( &chad, &HipsterBystander::SmugComment ); 130 | 131 | // Orcs and props take damage 132 | merlin.MysticalShotgunSignal.Connect( &rosencrantz, &Orc::TakeDamage ); 133 | merlin.MysticalShotgunSignal.Connect( &guildenstern, &Orc::TakeDamage ); 134 | merlin.MysticalShotgunSignal.Connect( &chair, &Prop::TakeDamage ); 135 | 136 | // Emit a signal 137 | merlin.BattleCry(); 138 | 139 | // Output: 140 | // This is my boomstick! - merlin, Wizard::BattleCry() 141 | // GRUMBLE GRUMBLE GRUMBLE... - rosencrantz, Orc::Retort() 142 | // GRUMBLE GRUMBLE GRUMBLE... - guildenstern, Orc::Retort() 143 | // Whatever, I think the first movie was better. chad, HipsterBystander::SmugComment() 144 | 145 | // Emit another signal 146 | merlin.FireMysticalShotgun( 20.f ); 147 | 148 | // Output: 149 | // BLAM! - merlin, Wizrd::FireMysticalShotgun() 150 | // Orc down! - rosencrantz, Orc::TakeDamage() 151 | // Orc down! - guildenstern, Orc::TakeDamage() 152 | // SMASH! - chair, Prop::TakeDamage() 153 | 154 | return 0; 155 | } 156 | ``` 157 | 158 | Usage notes 159 | ----------- 160 | 161 | ### Default initialization 162 | 163 | Both signal and observer objects need to be initialized with a `jl::ScopedAllocator`. You can set the allocator reference at the class level, or on a per-object basis. 164 | 165 | ##### Using the built-in allocators 166 | 167 | ```c++ 168 | #include "StaticSignalConnectionAllocators.h" 169 | 170 | enum { eMaxConnections = 50 }; 171 | jl::StaticSignalConnectionAllocator< eMaxConnections > g_oSignalConnectionAllocator; 172 | jl::StaticObserverConnectionAllocator< eMaxConnections > g_oObserverConnectionAllocator; 173 | 174 | void SomeInitializationFunction() 175 | { 176 | ... 177 | jl::SignalBase::SetCommonConnectionAllocator( &g_oSignalConnectionAllocator ); 178 | jl::SignalObserver::SetCommonConnectionAllocator( &g_oObserverConnectionAllocator ); 179 | ... 180 | } 181 | ``` 182 | 183 | `StaticSignalConnectionAllocators.h` provides fast, purpose-built allocators that can be declared statically. These allocator classes require you to know the maximum number of connections at compile time, although it's fairly trivial to adapt these pools to use runtime-allocated memory buffers instead. 184 | 185 | For many simple applications, you can just declare your connection allocators in the global scope. 186 | 187 | ### Declaring signals 188 | 189 | A signal object must be declared with a list of argument types. Slot functions whose parameter lists match the signal declaration can connect to the signal. 190 | 191 | There are three ways to declare a signal: 192 | 193 | ```c++ 194 | // 1. Macro declaration, with a comma-separated list of argument types. 195 | JL_SIGNAL( int, float, const char* ) oSignal; 196 | 197 | // 2. Template declaration, with an inferred parameter count. The JL_SIGNAL() macro expands to this form. 198 | jl::Signal< void(int, float, const char*) > oSignal; 199 | 200 | // 3. Template declaration, with an explicit parameter count in the typename. 201 | jl::Signal3< int, float, const char* > oSignal; 202 | ``` 203 | 204 | Signals can be declared for signatures with up to eight formal parameters. [Variadic functions](http://en.wikipedia.org/wiki/Variadic_function) are not supported. 205 | 206 | ### Connecting signals to functions 207 | 208 | You can connect signals to any function with a `void` return type, as long as the function's parameter list matches the signal's declared parameter types: 209 | 210 | ```c++ 211 | // OK 212 | JL_SIGNAL( int, float, const char* ) oSignal; 213 | void HandleTransaction( int nId, float fValue, const char* pUsername ); 214 | oSignal.Connect( &HandleTransaction ); 215 | 216 | // NOT OK: doesn't return void 217 | int HandleTransaction( int nId, float fValue, const char* pUsername ); 218 | 219 | // NOT OK: parameter types don't match signal declaration 220 | void HandleTransaction( int nId, double fValue, const char* pUsername ); 221 | ``` 222 | 223 | Note that by default, there is nothing stopping you from connecting the same function twice. If double-connections are a bad thing in your application, then you can uncomment the definition of `JL_SIGNAL_ASSERT_ON_DOUBLE_CONNECT` in `Signal.h` to trigger assertion failures when the same connection is made twice. This is potentially quite slow, so it's best used for debugging purposes only. 224 | 225 | ### Connecting signals to instance methods 226 | 227 | To connect a class's instance methods to a signal, you must derive the class from `jl::SignalObserver`. This guarantees that connections will be broken between signals and connected objects that have gone out of scope. 228 | 229 | As with non-instance functions, instance methods that connect to a signal must have a `void` return type. Signals do not discriminate over constness, so both `const` and non-`const` instance methods may be connected to the same signal. 230 | 231 | ```c++ 232 | JL_SIGNAL( int, float, const char* ) oSignal; 233 | 234 | class TransactionManager : public jl::SignalObserver 235 | { 236 | public: 237 | void HandleTransaction( int nId, float fValue, const char* pUsername ); 238 | }; 239 | 240 | class Logger : public jl::SignalObserver 241 | { 242 | public: 243 | void LogTransaction( int nId, float fValue, const char* pUsername ) const; 244 | }; 245 | 246 | TransactionManager* pTransactionManager = new TransactionManager; 247 | const Logger* pLogger = new pLogger; 248 | 249 | // Connect an object and non-const instance method to a signal 250 | oSignal.Connect( pTransactionManager, &TransactionManager::HandleTransaction ); 251 | 252 | // Connect an object and const instance method to a signal 253 | oSignal.Connect( pLogger, &Logger::LogTransaction ); 254 | ``` 255 | 256 | ### Emitting signals 257 | 258 | To emit a signal, simply call the `Emit()` method on the signal with arguments appropriate to the signal's parameter declaration: 259 | 260 | ```c++ 261 | JL_SIGNAL( int, float, const char* ) oSignal; 262 | oSignal.Emit( 5, 40.f, "hello world!" ); 263 | 264 | // You can also use operator(), which aliases Emit(). 265 | oSignal( 5, 40.f, "hello world!" ); 266 | ``` 267 | 268 | Connected functions will be called in the order that they were connected. 269 | 270 | ### Disconnection 271 | 272 | You don't need to manage signal disconnection when objects with connected instance methods go out of scope. The `jl::SignalObserver` base class ensures that any pointers to observers will be properly cleaned up. 273 | 274 | Several methods are available for manually disconnecting functions and observers from signals: 275 | 276 | ```c++ 277 | // Declare a signal 278 | JL_SIGNAL() oSignal; 279 | 280 | // Connect a non-instance function 281 | void DoSomethingGlobally(); 282 | oSignal.Connect( &DoSomethingGlobally ); 283 | 284 | // Disconnect non-instance functions 285 | oSignal.Disconnect( &DoSomethingGlobally ); 286 | 287 | // Connect instance methods 288 | class Foo : public jlSignalObserver 289 | { 290 | public: 291 | void Bar(); 292 | }; 293 | 294 | Foo oFoo; 295 | oSignal.Connect( &oFoo, &Foo::Bar ); 296 | 297 | // Disconnect instance method 298 | oSignal.Disconnect( &oFoo, &Foo::Bar ); 299 | 300 | // Disconnect all instance methods for an observer 301 | oSignal.Disconnect( &oFoo ); 302 | 303 | // Disconnect all connected functions and observers 304 | oSignal.DisconnectAll(); 305 | ``` 306 | 307 | ### Customized allocation schemes 308 | 309 | While the default allocators set globally should be enough for most applications, you can write adapters for whatever allocation scheme you like. The *reductio ad absurdum* case would be to wrap `malloc`, which would look something like the following: 310 | 311 | ```c++ 312 | #include 313 | 314 | class MallocAllocator : public jl::ScopedAllocator 315 | { 316 | public: 317 | virtual void* Alloc( size_t nBytes ) { return malloc( nBytes ); } 318 | virtual void Free( void* pObject ) { free( pObject ); } 319 | }; 320 | 321 | MallocAllocator g_oMallocAllocator; 322 | 323 | void SomeInitializationFunction() 324 | { 325 | ... 326 | jl::SignalBase::SetCommonConnectionAllocator( &g_oMallocAllocator ); 327 | jl::SignalObserver::SetCommonConnectionAllocator( &g_oMallocAllocator ); 328 | ... 329 | } 330 | ``` 331 | 332 | ##### Per-object allocators 333 | 334 | I've never come upon a use case that required per-object allocation, but the API allows it: 335 | 336 | ```c++ 337 | jl::ScopedAllocator* pSomeCustomAllocator; 338 | 339 | // Signals 340 | JL_SIGNAL() oSomeSignal( pSomeCustomAllocator ); 341 | 342 | // Observers 343 | class SomeObserver : public jl::SignalObserver 344 | { 345 | public: 346 | SomeObserver( jl::ScopedAllocator* pSignalConnectionAllocator ) : 347 | jl::SignalObserver( pSignalConnectionAllocator ) 348 | { 349 | ... 350 | } 351 | }; 352 | 353 | SomeObserver oSomeObserver( pSomeCustomAllocator ); 354 | ``` 355 | 356 | ### Caveat - modifying signals during an `Emit()` 357 | 358 | Be careful with logic in connected functions that could lead to modifying or destroying signal objects while they are still processing an `Emit()`. 359 | 360 | For example, consider the following code: 361 | 362 | ```c++ 363 | typedef JL_SIGNAL() TSignal; 364 | TSignal* pSignal = new TSignal; 365 | 366 | class Foo : public jl::SignalObserver 367 | { 368 | public: 369 | void OnSignal() { delete pSignal; } 370 | }; 371 | 372 | Foo* foo = new Foo; 373 | pSignal->Connect( &foo, &Foo::OnSignal ); 374 | pSignal->Emit(); // This causes pSignal to be deleted before Emit() returns! 375 | ``` 376 | 377 | Miscellaneous 378 | ------------- 379 | 380 | ### Compatibility 381 | 382 | I've used this library and/or similar protoypes with the following compilers: 383 | 384 | - LLVM/clang 385 | - GCC 386 | - Visual C++ 9 387 | - SNC Compiler 388 | 389 | Most incompatibility issues arise from the fact that [FastDelegate](http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible) employs some clever but non-standard hackery to store function pointers in generic structures. It's worth noting that LLVM/clang support isn't documented (indeed, the library was released at least two years before clang), but it seems to work in my admittedly-limited test cases. In general, compiler incompatibility with FastDelegate is very rare. 390 | 391 | ### License 392 | 393 | This code is public domain, with the following exceptions: 394 | 395 | - `FastDelegate.h` is released under the [Code Project Open License](http://www.codeproject.com/info/cpol10.aspx). 396 | -------------------------------------------------------------------------------- /src/SignalTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Signal.h" 5 | #include "StaticSignalConnectionAllocators.h" 6 | 7 | using namespace jl; 8 | 9 | namespace 10 | { 11 | enum { eSignalMaxArity = 8 }; 12 | 13 | class TestObserver : public SignalObserver 14 | { 15 | private: 16 | static int s_id; 17 | static unsigned s_pInstanceMethodCallsByArity[ eSignalMaxArity + 1 ]; 18 | static unsigned s_pStaticMethodCallsByArity[ eSignalMaxArity + 1 ]; 19 | 20 | int m_nId; 21 | 22 | public: 23 | static void ResetCallsByArity() 24 | { 25 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(s_pInstanceMethodCallsByArity); ++i ) 26 | { 27 | s_pInstanceMethodCallsByArity[i] = 0; 28 | s_pStaticMethodCallsByArity[i] = 0; 29 | } 30 | } 31 | 32 | static unsigned CountCallsByArity( unsigned arity ) 33 | { 34 | return s_pInstanceMethodCallsByArity[arity] + s_pStaticMethodCallsByArity[arity]; 35 | } 36 | 37 | TestObserver() 38 | { 39 | m_nId = s_id++; 40 | } 41 | 42 | void M0() 43 | { 44 | s_pInstanceMethodCallsByArity[0] += 1; 45 | printf( "TestObserver %d: instance method 0!\n", m_nId ); 46 | } 47 | 48 | void M1( int p1 ) 49 | { 50 | s_pInstanceMethodCallsByArity[1] += 1; 51 | printf( "TestObserver %d: instance method 1! %d\n", m_nId, p1 ); 52 | } 53 | 54 | void M2( int p1, float p2 ) 55 | { 56 | s_pInstanceMethodCallsByArity[2] += 1; 57 | printf( "TestObserver %d: instance method 2! %d %f\n", m_nId, p1, p2 ); 58 | } 59 | 60 | void M3( int p1, float p2, char p3 ) 61 | { 62 | s_pInstanceMethodCallsByArity[3] += 1; 63 | printf( "TestObserver %d: instance method 3! %d %f %c\n", m_nId, p1, p2, p3 ); 64 | } 65 | 66 | void M4( int p1, float p2, char p3, const char* p4 ) 67 | { 68 | s_pInstanceMethodCallsByArity[4] += 1; 69 | printf( "TestObserver %d: instance method 4! %d %f %c %s\n", m_nId, p1, p2, p3, p4 ); 70 | } 71 | 72 | void M5( int p1, float p2, char p3, const char* p4, int p5 ) 73 | { 74 | s_pInstanceMethodCallsByArity[5] += 1; 75 | printf( "TestObserver %d: instance method 5! %d %f %c %s %d\n", m_nId, p1, p2, p3, p4, p5 ); 76 | } 77 | 78 | void M6( int p1, float p2, char p3, const char* p4, int p5, float p6 ) 79 | { 80 | s_pInstanceMethodCallsByArity[6] += 1; 81 | printf( "TestObserver %d: instance method 6! %d %f %c %s %d %f\n", m_nId, p1, p2, p3, p4, p5, p6 ); 82 | } 83 | 84 | void M7( int p1, float p2, char p3, const char* p4, int p5, float p6, char p7 ) 85 | { 86 | s_pInstanceMethodCallsByArity[7] += 1; 87 | printf( "TestObserver %d: instance method 7! %d %f %c %s %d %f %c\n", m_nId, p1, p2, p3, p4, p5, p6, p7 ); 88 | } 89 | 90 | void M8( int p1, float p2, char p3, const char* p4, int p5, float p6, char p7, const char* p8 ) 91 | { 92 | s_pInstanceMethodCallsByArity[8] += 1; 93 | printf( "TestObserver %d: instance method 8! %d %f %c %s %d %f %c %s\n", m_nId, p1, p2, p3, p4, p5, p6, p7, p8 ); 94 | } 95 | 96 | void CM0() const 97 | { 98 | s_pInstanceMethodCallsByArity[0] += 1; 99 | printf( "TestObserver %d: const instance method 0!\n", m_nId ); 100 | } 101 | 102 | void CM1( int p1 ) const 103 | { 104 | s_pInstanceMethodCallsByArity[1] += 1; 105 | printf( "TestObserver %d: const instance method 1! %d\n", m_nId, p1 ); 106 | } 107 | 108 | void CM2( int p1, float p2 ) const 109 | { 110 | s_pInstanceMethodCallsByArity[2] += 1; 111 | printf( "TestObserver %d: const instance method 2! %d %f\n", m_nId, p1, p2 ); 112 | } 113 | 114 | void CM3( int p1, float p2, char p3 ) const 115 | { 116 | s_pInstanceMethodCallsByArity[3] += 1; 117 | printf( "TestObserver %d: const instance method 3! %d %f %c\n", m_nId, p1, p2, p3 ); 118 | } 119 | 120 | void CM4( int p1, float p2, char p3, const char* p4 ) const 121 | { 122 | s_pInstanceMethodCallsByArity[4] += 1; 123 | printf( "TestObserver %d: const instance method 4! %d %f %c %s\n", m_nId, p1, p2, p3, p4 ); 124 | } 125 | 126 | void CM5( int p1, float p2, char p3, const char* p4, int p5 ) const 127 | { 128 | s_pInstanceMethodCallsByArity[5] += 1; 129 | printf( "TestObserver %d: const instance method 5! %d %f %c %s %d\n", m_nId, p1, p2, p3, p4, p5 ); 130 | } 131 | 132 | void CM6( int p1, float p2, char p3, const char* p4, int p5, float p6 ) const 133 | { 134 | s_pInstanceMethodCallsByArity[6] += 1; 135 | printf( "TestObserver %d: const instance method 6! %d %f %c %s %d %f\n", m_nId, p1, p2, p3, p4, p5, p6 ); 136 | } 137 | 138 | void CM7( int p1, float p2, char p3, const char* p4, int p5, float p6, char p7 ) const 139 | { 140 | s_pInstanceMethodCallsByArity[7] += 1; 141 | printf( "TestObserver %d: const instance method 7! %d %f %c %s %d %f %c\n", m_nId, p1, p2, p3, p4, p5, p6, p7 ); 142 | } 143 | 144 | void CM8( int p1, float p2, char p3, const char* p4, int p5, float p6, char p7, const char* p8 ) const 145 | { 146 | s_pInstanceMethodCallsByArity[8] += 1; 147 | printf( "TestObserver %d: const instance method 8! %d %f %c %s %d %f %c %s\n", m_nId, p1, p2, p3, p4, p5, p6, p7, p8 ); 148 | } 149 | 150 | static void SM0() 151 | { 152 | s_pStaticMethodCallsByArity[0] += 1; 153 | printf( "TestObserver: static instance method 0!\n" ); 154 | } 155 | 156 | static void SM1( int p1 ) 157 | { 158 | s_pStaticMethodCallsByArity[1] += 1; 159 | printf( "TestObserver: static instance method 1! %d\n", p1 ); 160 | } 161 | 162 | static void SM2( int p1, float p2 ) 163 | { 164 | s_pStaticMethodCallsByArity[2] += 1; 165 | printf( "TestObserver: static instance method 2! %d %f\n", p1, p2 ); 166 | } 167 | 168 | static void SM3( int p1, float p2, char p3 ) 169 | { 170 | s_pStaticMethodCallsByArity[3] += 1; 171 | printf( "TestObserver: static instance method 3! %d %f %c\n", p1, p2, p3 ); 172 | } 173 | 174 | static void SM4( int p1, float p2, char p3, const char* p4 ) 175 | { 176 | s_pStaticMethodCallsByArity[4] += 1; 177 | printf( "TestObserver: static instance method 4! %d %f %c %s\n", p1, p2, p3, p4 ); 178 | } 179 | 180 | static void SM5( int p1, float p2, char p3, const char* p4, int p5 ) 181 | { 182 | s_pStaticMethodCallsByArity[5] += 1; 183 | printf( "TestObserver: static instance method 5! %d %f %c %s %d\n", p1, p2, p3, p4, p5 ); 184 | } 185 | 186 | static void SM6( int p1, float p2, char p3, const char* p4, int p5, float p6 ) 187 | { 188 | s_pStaticMethodCallsByArity[6] += 1; 189 | printf( "TestObserver: static instance method 6! %d %f %c %s %d %f\n", p1, p2, p3, p4, p5, p6 ); 190 | } 191 | 192 | static void SM7( int p1, float p2, char p3, const char* p4, int p5, float p6, char p7 ) 193 | { 194 | s_pStaticMethodCallsByArity[7] += 1; 195 | printf( "TestObserver: static instance method 7! %d %f %c %s %d %f %c\n", p1, p2, p3, p4, p5, p6, p7 ); 196 | } 197 | 198 | static void SM8( int p1, float p2, char p3, const char* p4, int p5, float p6, char p7, const char* p8 ) 199 | { 200 | s_pStaticMethodCallsByArity[8] += 1; 201 | printf( "TestObserver: static instance method 8! %d %f %c %s %d %f %c %s\n", p1, p2, p3, p4, p5, p6, p7, p8 ); 202 | } 203 | }; 204 | 205 | int TestObserver::s_id = 1; 206 | unsigned TestObserver::s_pInstanceMethodCallsByArity[ eSignalMaxArity + 1 ]; 207 | unsigned TestObserver::s_pStaticMethodCallsByArity[ eSignalMaxArity + 1 ]; 208 | } // anonymous namespace 209 | 210 | void SignalTest() 211 | { 212 | // Allocators 213 | enum { eMaxConnections = 500, eSignalMaxArgs = 8 }; 214 | StaticSignalConnectionAllocator< eMaxConnections > oSignalConnectionAllocator; 215 | StaticObserverConnectionAllocator< eMaxConnections > oObserverConnectionAllocator; 216 | 217 | jl::SignalBase::SetCommonConnectionAllocator( &oSignalConnectionAllocator ); 218 | jl::SignalObserver::SetCommonConnectionAllocator( &oObserverConnectionAllocator ); 219 | 220 | // Signals 221 | JL_SIGNAL() Sig0; 222 | JL_SIGNAL( int ) Sig1; 223 | JL_SIGNAL( int, float ) Sig2; 224 | JL_SIGNAL( int, float, char ) Sig3; 225 | JL_SIGNAL( int, float, char, const char* ) Sig4; 226 | JL_SIGNAL( int, float, char, const char*, int ) Sig5; 227 | JL_SIGNAL( int, float, char, const char*, int, float ) Sig6; 228 | JL_SIGNAL( int, float, char, const char*, int, float, char ) Sig7; 229 | JL_SIGNAL( int, float, char, const char*, int, float, char, const char* ) Sig8; 230 | 231 | const SignalBase* const ppSignalsByArity[] = { 232 | & Sig0, & Sig1, & Sig2, & Sig3, & Sig4, & Sig5, & Sig6, & Sig7, & Sig8 233 | }; 234 | 235 | // Observers 236 | TestObserver pObservers[ 16 ]; 237 | 238 | // Test connections 239 | printf( "Connection non-const instance methods to signals...\n" ); 240 | 241 | for ( int i = 0; i < JL_ARRAY_SIZE(pObservers); ++i ) 242 | { 243 | Sig0.Connect( & pObservers[i], & TestObserver::M0 ); 244 | assert( Sig0.IsConnected(& pObservers[i], & TestObserver::M0) ); 245 | 246 | Sig1.Connect( & pObservers[i], & TestObserver::M1 ); 247 | assert( Sig1.IsConnected(& pObservers[i], & TestObserver::M1) ); 248 | 249 | const int nIndex = i + 1; 250 | 251 | if ( nIndex % 2 == 0 ) 252 | { 253 | Sig2.Connect( & pObservers[i], & TestObserver::M2 ); 254 | assert( Sig2.IsConnected(& pObservers[i], & TestObserver::M2) ); 255 | } 256 | 257 | if ( nIndex % 3 == 0 ) 258 | { 259 | Sig3.Connect( & pObservers[i], & TestObserver::M3 ); 260 | assert( Sig3.IsConnected(& pObservers[i], & TestObserver::M3) ); 261 | } 262 | 263 | if ( nIndex % 4 == 0 ) 264 | { 265 | Sig4.Connect( & pObservers[i], & TestObserver::M4 ); 266 | assert( Sig4.IsConnected(& pObservers[i], & TestObserver::M4) ); 267 | } 268 | 269 | if ( nIndex % 5 == 0 ) 270 | { 271 | Sig5.Connect( & pObservers[i], & TestObserver::M5 ); 272 | assert( Sig5.IsConnected(& pObservers[i], & TestObserver::M5) ); 273 | } 274 | 275 | if ( nIndex % 6 == 0 ) 276 | { 277 | Sig6.Connect( & pObservers[i], & TestObserver::M6 ); 278 | assert( Sig6.IsConnected(& pObservers[i], & TestObserver::M6) ); 279 | } 280 | 281 | if ( nIndex % 7 == 0 ) 282 | { 283 | Sig7.Connect( & pObservers[i], & TestObserver::M7 ); 284 | assert( Sig7.IsConnected(& pObservers[i], & TestObserver::M7) ); 285 | } 286 | 287 | if ( nIndex % 8 == 0 ) 288 | { 289 | Sig8.Connect( & pObservers[i], & TestObserver::M8 ); 290 | assert( Sig8.IsConnected(& pObservers[i], & TestObserver::M8) ); 291 | } 292 | } 293 | 294 | printf( "Firing signals...\n" ); 295 | 296 | // Zero the received call count 297 | TestObserver::ResetCallsByArity(); 298 | 299 | // Emit signals 300 | Sig0(); 301 | Sig1( 1 ); 302 | Sig2( 1, 2.0f ); 303 | Sig3( 1, 2.0f, 'T' ); 304 | Sig4( 1, 2.0f, 'T', "Four" ); 305 | Sig5( 1, 2.0f, 'T', "Four", 5 ); 306 | Sig6( 1, 2.0f, 'T', "Four", 5, 6.0f ); 307 | Sig7( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S' ); 308 | Sig8( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S', "Eight" ); 309 | 310 | // Verify that the observer count is equal to the received call count 311 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(ppSignalsByArity); ++i ) 312 | { 313 | const unsigned nConnections = ppSignalsByArity[i]->CountConnections(); 314 | const unsigned nCalls = TestObserver::CountCallsByArity( i ); 315 | printf( "Arity %d, Connections: %d, Calls: %d\n", i, nConnections, nCalls); 316 | assert( ppSignalsByArity[i]->CountConnections() == TestObserver::CountCallsByArity(i) ); 317 | } 318 | 319 | // Test const connections 320 | printf( "Connecting const instance methods to signals...\n" ); 321 | 322 | for ( int i = 0; i < JL_ARRAY_SIZE(pObservers); ++i ) 323 | { 324 | Sig0.Connect( & pObservers[i], & TestObserver::CM0 ); 325 | assert( Sig0.IsConnected(& pObservers[i], & TestObserver::CM0) ); 326 | 327 | Sig1.Connect( & pObservers[i], & TestObserver::CM1 ); 328 | assert( Sig1.IsConnected(& pObservers[i], & TestObserver::CM1) ); 329 | 330 | const int nIndex = (JL_ARRAY_SIZE(pObservers) + 1 - i); 331 | 332 | if ( nIndex % 2 == 0 ) 333 | { 334 | Sig2.Connect( & pObservers[i], & TestObserver::CM2 ); 335 | assert( Sig2.IsConnected(& pObservers[i], & TestObserver::CM2) ); 336 | } 337 | 338 | if ( nIndex % 3 == 0 ) 339 | { 340 | Sig3.Connect( & pObservers[i], & TestObserver::CM3 ); 341 | assert( Sig3.IsConnected(& pObservers[i], & TestObserver::CM3) ); 342 | } 343 | 344 | if ( nIndex % 4 == 0 ) 345 | { 346 | Sig4.Connect( & pObservers[i], & TestObserver::CM4 ); 347 | assert( Sig4.IsConnected(& pObservers[i], & TestObserver::CM4) ); 348 | } 349 | 350 | if ( nIndex % 5 == 0 ) 351 | { 352 | Sig5.Connect( & pObservers[i], & TestObserver::CM5 ); 353 | assert( Sig5.IsConnected(& pObservers[i], & TestObserver::CM5) ); 354 | } 355 | 356 | if ( nIndex % 6 == 0 ) 357 | { 358 | Sig6.Connect( & pObservers[i], & TestObserver::CM6 ); 359 | assert( Sig6.IsConnected(& pObservers[i], & TestObserver::CM6) ); 360 | } 361 | 362 | if ( nIndex % 7 == 0 ) 363 | { 364 | Sig7.Connect( & pObservers[i], & TestObserver::CM7 ); 365 | assert( Sig7.IsConnected(& pObservers[i], & TestObserver::CM7) ); 366 | } 367 | 368 | if ( nIndex % 8 == 0 ) 369 | { 370 | Sig8.Connect( & pObservers[i], & TestObserver::CM8 ); 371 | assert( Sig8.IsConnected(& pObservers[i], & TestObserver::CM8) ); 372 | } 373 | } 374 | 375 | printf( "Firing signals...\n" ); 376 | 377 | // Zero the received call count 378 | TestObserver::ResetCallsByArity(); 379 | 380 | // Emit signals 381 | Sig0(); 382 | Sig1( 1 ); 383 | Sig2( 1, 2.0f ); 384 | Sig3( 1, 2.0f, 'T' ); 385 | Sig4( 1, 2.0f, 'T', "Four" ); 386 | Sig5( 1, 2.0f, 'T', "Four", 5 ); 387 | Sig6( 1, 2.0f, 'T', "Four", 5, 6.0f ); 388 | Sig7( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S' ); 389 | Sig8( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S', "Eight" ); 390 | 391 | // Verify that the observer count is equal to the received call count 392 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(ppSignalsByArity); ++i ) 393 | { 394 | const unsigned nConnections = ppSignalsByArity[i]->CountConnections(); 395 | const unsigned nCalls = TestObserver::CountCallsByArity( i ); 396 | printf( "Arity %d, Connections: %d, Calls: %d\n", i, nConnections, nCalls); 397 | assert( ppSignalsByArity[i]->CountConnections() == TestObserver::CountCallsByArity(i) ); 398 | } 399 | 400 | // Test static connections 401 | printf( "Connecting static methods to signals...\n" ); 402 | 403 | Sig0.Connect( & TestObserver::SM0 ); 404 | assert( Sig0.IsConnected(& TestObserver::SM0) ); 405 | 406 | Sig1.Connect( & TestObserver::SM1 ); 407 | assert( Sig1.IsConnected(& TestObserver::SM1) ); 408 | 409 | Sig2.Connect( & TestObserver::SM2 ); 410 | assert( Sig2.IsConnected(& TestObserver::SM2) ); 411 | 412 | Sig3.Connect( & TestObserver::SM3 ); 413 | assert( Sig3.IsConnected(& TestObserver::SM3) ); 414 | 415 | Sig4.Connect( & TestObserver::SM4 ); 416 | assert( Sig4.IsConnected(& TestObserver::SM4) ); 417 | 418 | Sig5.Connect( & TestObserver::SM5 ); 419 | assert( Sig5.IsConnected(& TestObserver::SM5) ); 420 | 421 | Sig6.Connect( & TestObserver::SM6 ); 422 | assert( Sig6.IsConnected(& TestObserver::SM6) ); 423 | 424 | Sig7.Connect( & TestObserver::SM7 ); 425 | assert( Sig7.IsConnected(& TestObserver::SM7) ); 426 | 427 | Sig8.Connect( & TestObserver::SM8 ); 428 | assert( Sig8.IsConnected(& TestObserver::SM8) ); 429 | 430 | printf( "Firing signals...\n" ); 431 | 432 | // Zero the received call count 433 | TestObserver::ResetCallsByArity(); 434 | 435 | // Emit signals 436 | Sig0(); 437 | Sig1( 1 ); 438 | Sig2( 1, 2.0f ); 439 | Sig3( 1, 2.0f, 'T' ); 440 | Sig4( 1, 2.0f, 'T', "Four" ); 441 | Sig5( 1, 2.0f, 'T', "Four", 5 ); 442 | Sig6( 1, 2.0f, 'T', "Four", 5, 6.0f ); 443 | Sig7( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S' ); 444 | Sig8( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S', "Eight" ); 445 | 446 | // Verify that the observer count is equal to the received call count 447 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(ppSignalsByArity); ++i ) 448 | { 449 | const unsigned nConnections = ppSignalsByArity[i]->CountConnections(); 450 | const unsigned nCalls = TestObserver::CountCallsByArity( i ); 451 | printf( "Arity %d, Connections: %d, Calls: %d\n", i, nConnections, nCalls); 452 | assert( ppSignalsByArity[i]->CountConnections() == TestObserver::CountCallsByArity(i) ); 453 | } 454 | 455 | // Test disconnections 456 | printf( "Disconnecting non-const instance methods to signals...\n" ); 457 | 458 | for ( int i = 0; i < JL_ARRAY_SIZE(pObservers); ++i ) 459 | { 460 | Sig0.Disconnect( & pObservers[i], & TestObserver::M0 ); 461 | assert( ! Sig0.IsConnected(& pObservers[i], & TestObserver::M0) ); 462 | 463 | Sig1.Disconnect( & pObservers[i], & TestObserver::M1 ); 464 | assert( ! Sig1.IsConnected(& pObservers[i], & TestObserver::M1) ); 465 | 466 | const int nIndex = i + 1; 467 | 468 | if ( nIndex % 2 == 0 ) 469 | { 470 | Sig2.Disconnect( & pObservers[i], & TestObserver::M2 ); 471 | assert( ! Sig2.IsConnected(& pObservers[i], & TestObserver::M2) ); 472 | } 473 | 474 | if ( nIndex % 3 == 0 ) 475 | { 476 | Sig3.Disconnect( & pObservers[i], & TestObserver::M3 ); 477 | assert( ! Sig3.IsConnected(& pObservers[i], & TestObserver::M3) ); 478 | } 479 | 480 | if ( nIndex % 4 == 0 ) 481 | { 482 | Sig4.Disconnect( & pObservers[i], & TestObserver::M4 ); 483 | assert( ! Sig4.IsConnected(& pObservers[i], & TestObserver::M4) ); 484 | } 485 | 486 | if ( nIndex % 5 == 0 ) 487 | { 488 | Sig5.Disconnect( & pObservers[i], & TestObserver::M5 ); 489 | assert( ! Sig5.IsConnected(& pObservers[i], & TestObserver::M5) ); 490 | } 491 | 492 | if ( nIndex % 6 == 0 ) 493 | { 494 | Sig6.Disconnect( & pObservers[i], & TestObserver::M6 ); 495 | assert( ! Sig6.IsConnected(& pObservers[i], & TestObserver::M6) ); 496 | } 497 | 498 | if ( nIndex % 7 == 0 ) 499 | { 500 | Sig7.Disconnect( & pObservers[i], & TestObserver::M7 ); 501 | assert( ! Sig7.IsConnected(& pObservers[i], & TestObserver::M7) ); 502 | } 503 | 504 | if ( nIndex % 8 == 0 ) 505 | { 506 | Sig8.Disconnect( & pObservers[i], & TestObserver::M8 ); 507 | assert( ! Sig8.IsConnected(& pObservers[i], & TestObserver::M8) ); 508 | } 509 | } 510 | 511 | printf( "Firing signals...\n" ); 512 | 513 | // Zero the received call count 514 | TestObserver::ResetCallsByArity(); 515 | 516 | // Emit signals 517 | Sig0(); 518 | Sig1( 1 ); 519 | Sig2( 1, 2.0f ); 520 | Sig3( 1, 2.0f, 'T' ); 521 | Sig4( 1, 2.0f, 'T', "Four" ); 522 | Sig5( 1, 2.0f, 'T', "Four", 5 ); 523 | Sig6( 1, 2.0f, 'T', "Four", 5, 6.0f ); 524 | Sig7( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S' ); 525 | Sig8( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S', "Eight" ); 526 | 527 | // Verify that the observer count is equal to the received call count 528 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(ppSignalsByArity); ++i ) 529 | { 530 | const unsigned nConnections = ppSignalsByArity[i]->CountConnections(); 531 | const unsigned nCalls = TestObserver::CountCallsByArity( i ); 532 | printf( "Arity %d, Connections: %d, Calls: %d\n", i, nConnections, nCalls); 533 | assert( ppSignalsByArity[i]->CountConnections() == TestObserver::CountCallsByArity(i) ); 534 | } 535 | 536 | printf( "Disconnecting const instance methods to signals...\n" ); 537 | 538 | for ( int i = 0; i < JL_ARRAY_SIZE(pObservers); ++i ) 539 | { 540 | Sig0.Disconnect( & pObservers[i], & TestObserver::CM0 ); 541 | assert( ! Sig0.IsConnected(& pObservers[i], & TestObserver::CM0) ); 542 | Sig1.Disconnect( & pObservers[i], & TestObserver::CM1 ); 543 | assert( ! Sig1.IsConnected(& pObservers[i], & TestObserver::CM1) ); 544 | 545 | const int nIndex = (JL_ARRAY_SIZE(pObservers) + 1 - i); 546 | 547 | if ( nIndex % 2 == 0 ) 548 | { 549 | Sig2.Disconnect( & pObservers[i], & TestObserver::CM2 ); 550 | assert( ! Sig2.IsConnected(& pObservers[i], & TestObserver::CM2) ); 551 | } 552 | 553 | if ( nIndex % 3 == 0 ) 554 | { 555 | Sig3.Disconnect( & pObservers[i], & TestObserver::CM3 ); 556 | assert( ! Sig3.IsConnected(& pObservers[i], & TestObserver::CM3) ); 557 | } 558 | 559 | if ( nIndex % 4 == 0 ) 560 | { 561 | Sig4.Disconnect( & pObservers[i], & TestObserver::CM4 ); 562 | assert( ! Sig4.IsConnected(& pObservers[i], & TestObserver::CM4) ); 563 | } 564 | 565 | if ( nIndex % 5 == 0 ) 566 | { 567 | Sig5.Disconnect( & pObservers[i], & TestObserver::CM5 ); 568 | assert( ! Sig5.IsConnected(& pObservers[i], & TestObserver::CM5) ); 569 | } 570 | 571 | if ( nIndex % 6 == 0 ) 572 | { 573 | Sig6.Disconnect( & pObservers[i], & TestObserver::CM6 ); 574 | assert( ! Sig6.IsConnected(& pObservers[i], & TestObserver::CM6) ); 575 | } 576 | 577 | if ( nIndex % 7 == 0 ) 578 | { 579 | Sig7.Disconnect( & pObservers[i], & TestObserver::CM7 ); 580 | assert( ! Sig7.IsConnected(& pObservers[i], & TestObserver::CM7) ); 581 | } 582 | 583 | if ( nIndex % 8 == 0 ) 584 | { 585 | Sig8.Disconnect( & pObservers[i], & TestObserver::CM8 ); 586 | assert( ! Sig8.IsConnected(& pObservers[i], & TestObserver::CM8) ); 587 | } 588 | } 589 | 590 | printf( "Firing signals...\n" ); 591 | 592 | // Zero the received call count 593 | TestObserver::ResetCallsByArity(); 594 | 595 | // Emit signals 596 | Sig0(); 597 | Sig1( 1 ); 598 | Sig2( 1, 2.0f ); 599 | Sig3( 1, 2.0f, 'T' ); 600 | Sig4( 1, 2.0f, 'T', "Four" ); 601 | Sig5( 1, 2.0f, 'T', "Four", 5 ); 602 | Sig6( 1, 2.0f, 'T', "Four", 5, 6.0f ); 603 | Sig7( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S' ); 604 | Sig8( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S', "Eight" ); 605 | 606 | // Verify that the observer count is equal to the received call count 607 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(ppSignalsByArity); ++i ) 608 | { 609 | const unsigned nConnections = ppSignalsByArity[i]->CountConnections(); 610 | const unsigned nCalls = TestObserver::CountCallsByArity( i ); 611 | printf( "Arity %d, Connections: %d, Calls: %d\n", i, nConnections, nCalls); 612 | assert( ppSignalsByArity[i]->CountConnections() == TestObserver::CountCallsByArity(i) ); 613 | } 614 | 615 | // Test static method disconnections 616 | printf( "Disconnecting static methods...\n" ); 617 | 618 | Sig0.Disconnect( & TestObserver::SM0 ); 619 | assert( ! Sig0.IsConnected(& TestObserver::SM0) ); 620 | 621 | Sig1.Disconnect( & TestObserver::SM1 ); 622 | assert( ! Sig1.IsConnected(& TestObserver::SM1) ); 623 | 624 | Sig2.Disconnect( & TestObserver::SM2 ); 625 | assert( ! Sig2.IsConnected(& TestObserver::SM2) ); 626 | 627 | Sig3.Disconnect( & TestObserver::SM3 ); 628 | assert( ! Sig3.IsConnected(& TestObserver::SM3) ); 629 | 630 | Sig4.Disconnect( & TestObserver::SM4 ); 631 | assert( ! Sig4.IsConnected(& TestObserver::SM4) ); 632 | 633 | Sig5.Disconnect( & TestObserver::SM5 ); 634 | assert( ! Sig5.IsConnected(& TestObserver::SM5) ); 635 | 636 | Sig6.Disconnect( & TestObserver::SM6 ); 637 | assert( ! Sig6.IsConnected(& TestObserver::SM6) ); 638 | 639 | Sig7.Disconnect( & TestObserver::SM7 ); 640 | assert( ! Sig7.IsConnected(& TestObserver::SM7) ); 641 | 642 | Sig8.Disconnect( & TestObserver::SM8 ); 643 | assert( ! Sig8.IsConnected(& TestObserver::SM8) ); 644 | 645 | printf( "Firing signals...\n" ); 646 | 647 | // Zero the received call count 648 | TestObserver::ResetCallsByArity(); 649 | 650 | // Emit signals 651 | Sig0(); 652 | Sig1( 1 ); 653 | Sig2( 1, 2.0f ); 654 | Sig3( 1, 2.0f, 'T' ); 655 | Sig4( 1, 2.0f, 'T', "Four" ); 656 | Sig5( 1, 2.0f, 'T', "Four", 5 ); 657 | Sig6( 1, 2.0f, 'T', "Four", 5, 6.0f ); 658 | Sig7( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S' ); 659 | Sig8( 1, 2.0f, 'T', "Four", 5, 6.0f, 'S', "Eight" ); 660 | 661 | // Verify that the observer count is equal to the received call count 662 | for ( unsigned i = 0; i < JL_ARRAY_SIZE(ppSignalsByArity); ++i ) 663 | { 664 | const unsigned nConnections = ppSignalsByArity[i]->CountConnections(); 665 | const unsigned nCalls = TestObserver::CountCallsByArity( i ); 666 | printf( "Arity %d, Connections: %d, Calls: %d\n", i, nConnections, nCalls); 667 | assert( ppSignalsByArity[i]->CountConnections() == TestObserver::CountCallsByArity(i) ); 668 | } 669 | } --------------------------------------------------------------------------------