Repository: larspensjo/SimpleSignal Branch: master Commit: 0c337f1a8fbd Files: 4 Total size: 15.6 KB Directory structure: gitextract_60vfiz5h/ ├── .gitignore ├── README.md ├── SimpleSignal.h └── test.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /*.exe ================================================ FILE: README.md ================================================ # High performance C++11 signals See [Performance of a C++11 Signal System](http://www.testbit.eu/2013/cpp11-signal-system-performance/) for the original source code, as well as performance measurements compared to other signalling systems. The original author, Tim Janik, licensed the source code to the public domain [CC0 1.0 Universal (CC0 1.0)](http://creativecommons.org/publicdomain/zero/1.0/). ## Declare a signal This example declares a signal 'sig' that takes three arguments and returns a char. ```c++ Simple::Signal sig; ``` ## Connect to a signal This example connects 'sig' to a static function. It is also possible to connect to member functions or lambda functions. ```c++ static char float_callback (float f, int, std::string) { // ... return 0; } void Init() { sig.connect(float_callback); } ``` ## Fire a signal ```c++ void func() { // ... sig.emit(1.0f, 7, "xxxx"); } ``` ================================================ FILE: SimpleSignal.h ================================================ // CC0 Public Domain: http://creativecommons.org/publicdomain/zero/1.0/ #pragma once #include #include #include #include #include namespace Simple { namespace Lib { /// ProtoSignal is the template implementation for callback list. template class ProtoSignal; // undefined /// CollectorInvocation invokes signal handlers differently depending on return type. template struct CollectorInvocation; /// CollectorLast returns the result of the last signal handler from a signal emission. template struct CollectorLast { using CollectorResult = Result; explicit CollectorLast () : last_() {} inline bool operator() (Result r) { last_ = r; return true; } CollectorResult result () { return last_; } private: Result last_; }; /// CollectorDefault implements the default signal handler collection behaviour. template struct CollectorDefault : CollectorLast {}; /// CollectorDefault specialisation for signals with void return type. template<> struct CollectorDefault { using CollectorResult = void; void result () {} inline bool operator() (void) { return true; } }; /// CollectorInvocation specialisation for regular signals. template struct CollectorInvocation { inline bool invoke (Collector &collector, const std::function &cbf, Args... args) const { return collector (cbf (args...)); } }; /// CollectorInvocation specialisation for signals with void return type. template struct CollectorInvocation { inline bool invoke (Collector &collector, const std::function &cbf, Args... args) const { cbf (args...); return collector(); } }; /// ProtoSignal template specialised for the callback signature and collector. template class ProtoSignal : private CollectorInvocation { protected: using CbFunction = std::function; using Result = typename CbFunction::result_type; using CollectorResult = typename Collector::CollectorResult; private: /*copy-ctor*/ ProtoSignal (const ProtoSignal&) = delete; ProtoSignal& operator= (const ProtoSignal&) = delete; using CallbackSlot = std::shared_ptr; using CallbackList = std::list; CallbackList callback_list_; size_t add_cb(const CbFunction& cb) { callback_list_.emplace_back(std::make_shared(cb)); return size_t (callback_list_.back().get()); } bool remove_cb(size_t id) { auto it =std::remove_if(begin(callback_list_), end(callback_list_), [id](const CallbackSlot& slot) { return size_t(slot.get()) == id; }); bool const removed = it != end(callback_list_); callback_list_.erase(it, end(callback_list_)); return removed; } public: /// ProtoSignal constructor, connects default callback if non-nullptr. ProtoSignal (const CbFunction &method) { if (method) add_cb(method); } /// ProtoSignal destructor releases all resources associated with this signal. ~ProtoSignal () { } /// Operator to add a new function or lambda as signal handler, returns a handler connection ID. size_t connect (const CbFunction &cb) { return add_cb(cb); } /// Operator to remove a signal handler through it connection ID, returns if a handler was removed. bool disconnect (size_t connection) { return remove_cb(connection); } /// Emit a signal, i.e. invoke all its callbacks and collect return types with the Collector. CollectorResult emit (Args... args) const { Collector collector; for (auto &slot : callback_list_) { if (slot) { const bool continue_emission = this->invoke (collector, *slot, args...); if (!continue_emission) break; } } return collector.result(); } // Number of connected slots. std::size_t size () const { return callback_list_.size(); } }; } // Lib // namespace Simple /** * Signal is a template type providing an interface for arbitrary callback lists. * A signal type needs to be declared with the function signature of its callbacks, * and optionally a return result collector class type. * Signal callbacks can be added with operator+= to a signal and removed with operator-=, using * a callback connection ID return by operator+= as argument. * The callbacks of a signal are invoked with the emit() method and arguments according to the signature. * The result returned by emit() depends on the signal collector class. By default, the result of * the last callback is returned from emit(). Collectors can be implemented to accumulate callback * results or to halt a running emissions in correspondance to callback results. * The signal implementation is safe against recursion, so callbacks may be removed and * added during a signal emission and recursive emit() calls are also safe. * The overhead of an unused signal is intentionally kept very low, around the size of a single pointer. * Note that the Signal template types is non-copyable. */ template ::result_type> > struct Signal /*final*/ : Lib::ProtoSignal { using ProtoSignal = Lib::ProtoSignal; using CbFunction = typename ProtoSignal::CbFunction; /// Signal constructor, supports a default callback as argument. Signal (const CbFunction &method = CbFunction()) : ProtoSignal (method) {} }; /// This function creates a std::function by binding @a object to the member function pointer @a method. template std::function slot (Instance &object, R (Class::*method) (Args...)) { return [&object, method] (Args... args) { return (object .* method) (args...); }; } /// This function creates a std::function by binding @a object to the member function pointer @a method. template std::function slot (Class *object, R (Class::*method) (Args...)) { return [object, method] (Args... args) { return (object ->* method) (args...); }; } /// Keep signal emissions going while all handlers return !0 (true). template struct CollectorUntil0 { using CollectorResult = Result; explicit CollectorUntil0 () : result_() {} const CollectorResult& result () { return result_; } inline bool operator() (Result r) { result_ = r; return result_ ? true : false; } private: CollectorResult result_; }; /// Keep signal emissions going while all handlers return 0 (false). template struct CollectorWhile0 { using CollectorResult = Result; explicit CollectorWhile0 () : result_() {} const CollectorResult& result () { return result_; } inline bool operator() (Result r) { result_ = r; return result_ ? false : true; } private: CollectorResult result_; }; /// CollectorVector returns the result of the all signal handlers from a signal emission in a std::vector. template struct CollectorVector { using CollectorResult = std::vector; const CollectorResult& result () { return result_; } inline bool operator() (Result r) { result_.push_back (r); return true; } private: CollectorResult result_; }; } // Simple ================================================ FILE: test.cpp ================================================ #include "SimpleSignal.h" // g++ -Wall -O2 -std=gnu++11 -pthread test.cpp && ./a.out // append -fsanitize=address for memory debugging #include #include #include #include #include #include #ifndef _MSC_VER #include #include static std::string string_printf (const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); #endif static std::string string_printf (const char *format, ...) { std::string result; char str[1000]; va_list args; va_start (args, format); if (vsnprintf (str, sizeof str, format, args) >= 0) result = str; va_end (args); return result; } static uint64_t timestamp_benchmark () { auto now = std::clock(); return 1.0e9 / CLOCKS_PER_SEC * now; } struct TestCounter { static uint64_t get (); static void set (uint64_t); static void add2 (void*, uint64_t); }; namespace { // Anon void (*test_counter_add2) (void*, uint64_t) = TestCounter::add2; // external symbol to prevent easy inlining static uint64_t test_counter_var = 0; } // Anon class BasicSignalTests { static std::string accu; struct Foo { char foo_bool (float f, int i, std::string s) { accu += string_printf ("Foo: %.2f\n", f + i + s.size()); return true; } }; static char float_callback (float f, int, std::string) { accu += string_printf ("float: %.2f\n", f); return 0; } public: static void run() { accu = ""; Simple::Signal sig1; size_t id1 = sig1.connect(float_callback); size_t id2 = sig1.connect([] (float, int i, std::string) { accu += string_printf ("int: %d\n", i); return 0; }); size_t id3 = sig1.connect([] (float, int, const std::string &s) { accu += string_printf ("string: %s\n", s.c_str()); return 0; }); sig1.emit (.3, 4, "huhu"); bool success; success = sig1.disconnect(id1); assert (success == true); success = sig1.disconnect(id1); assert (success == false); success = sig1.disconnect(id2); assert (success == true); success = sig1.disconnect(id3); assert (success == true); success = sig1.disconnect(id3); assert (success == false); success = sig1.disconnect(id2); assert (success == false); Foo foo; sig1.connect(Simple::slot (foo, &Foo::foo_bool)); sig1.connect(Simple::slot (&foo, &Foo::foo_bool)); sig1.emit (.5, 1, "12"); Simple::Signal sig2; sig2.connect([] (std::string msg, int) { accu += string_printf ("msg: %s", msg.c_str()); }); sig2.connect([] (std::string, int d) { accu += string_printf (" *%d*\n", d); }); sig2.emit ("in sig2", 17); accu += "DONE"; const char *expected = "float: 0.30\n" "int: 4\n" "string: huhu\n" "Foo: 3.50\n" "Foo: 3.50\n" "msg: in sig2 *17*\n" "DONE"; assert (accu == expected); } }; std::string BasicSignalTests::accu; class TestCollectorVector { static int handler1 () { return 1; } static int handler42 () { return 42; } static int handler777 () { return 777; } public: static void run () { Simple::Signal> sig_vector; sig_vector.connect(handler777); sig_vector.connect(handler42); sig_vector.connect(handler1); sig_vector.connect(handler42); sig_vector.connect(handler777); std::vector results = sig_vector.emit(); const std::vector reference = { 777, 42, 1, 42, 777, }; assert(5 == sig_vector.size()); assert (results == reference); } }; class TestCollectorUntil0 { bool check1, check2; TestCollectorUntil0() : check1 (0), check2 (0) {} bool handler_true () { check1 = true; return true; } bool handler_false () { check2 = true; return false; } bool handler_abort () { std::abort(); } public: static void run () { TestCollectorUntil0 self; Simple::Signal> sig_until0; sig_until0.connect(Simple::slot (self, &TestCollectorUntil0::handler_true)); sig_until0.connect(Simple::slot (self, &TestCollectorUntil0::handler_false)); sig_until0.connect(Simple::slot (self, &TestCollectorUntil0::handler_abort)); assert (!self.check1 && !self.check2); const bool result = sig_until0.emit(); assert (!result && self.check1 && self.check2); } }; class TestCollectorWhile0 { bool check1, check2; TestCollectorWhile0() : check1 (0), check2 (0) {} bool handler_0 () { check1 = true; return false; } bool handler_1 () { check2 = true; return true; } bool handler_abort () { std::abort(); } public: static void run () { TestCollectorWhile0 self; Simple::Signal> sig_while0; sig_while0.connect(Simple::slot (self, &TestCollectorWhile0::handler_0)); sig_while0.connect(Simple::slot (self, &TestCollectorWhile0::handler_1)); sig_while0.connect(Simple::slot (self, &TestCollectorWhile0::handler_abort)); assert (!self.check1 && !self.check2); const bool result = sig_while0.emit(); assert (result == true && self.check1 && self.check2); } }; static void bench_simple_signal() { Simple::Signal sig_increment; sig_increment.connect(test_counter_add2); const uint64_t start_counter = TestCounter::get(); const uint64_t benchstart = timestamp_benchmark(); uint64_t i; for (i = 0; i < 999999; i++) { sig_increment.emit (nullptr, 1); } const uint64_t benchdone = timestamp_benchmark(); const uint64_t end_counter = TestCounter::get(); assert (end_counter - start_counter == i); printf ("OK\n Benchmark: Simple::Signal: %fns per emission (size=%u): ", size_t (benchdone - benchstart) * 1.0 / size_t (i), (unsigned int) sizeof (sig_increment)); } static void bench_callback_loop() { void (*counter_increment) (void*, uint64_t) = test_counter_add2; const uint64_t start_counter = TestCounter::get(); const uint64_t benchstart = timestamp_benchmark(); uint64_t i; for (i = 0; i < 999999; i++) { counter_increment (nullptr, 1); } const uint64_t benchdone = timestamp_benchmark(); const uint64_t end_counter = TestCounter::get(); assert (end_counter - start_counter == i); printf ("OK\n Benchmark: callback loop: %fns per round: ", size_t (benchdone - benchstart) * 1.0 / size_t (i)); } uint64_t TestCounter::get () { return test_counter_var; } void TestCounter::set (uint64_t v) { test_counter_var = v; } void TestCounter::add2 (void*, uint64_t v) { test_counter_var += v; } int main (int argc, char *argv[]) { printf ("Signal/Basic Tests: "); BasicSignalTests::run(); printf ("OK\n"); printf ("Signal/CollectorVector: "); TestCollectorVector::run(); printf ("OK\n"); printf ("Signal/CollectorUntil0: "); TestCollectorUntil0::run(); printf ("OK\n"); printf ("Signal/CollectorWhile0: "); TestCollectorWhile0::run(); printf ("OK\n"); printf ("Signal/Benchmark: Simple::Signal: "); bench_simple_signal(); printf ("OK\n"); printf ("Signal/Benchmark: callback loop: "); bench_callback_loop(); printf ("OK\n"); return 0; }